React18 学习笔记
创建 demo 工程
命令为 npx create-react-app react-basic
严格模式
React18 默认创建的工程开启了严格模式。React 应用在开发环境下若使用了 React 的严格模式(<React.StrictMode>
),在渲染过程中,所有组件都会被调用两次。这是为了帮助检测不严谨的代码和潜在的问题。
具体在 src\index.js
中将 <React.StrictMode>
标签去掉即可
即:
JSX 概念
JSX 是 JavaScript 和 XML(HTML)的缩写,并非标准的 JS 语法,而是一种语法扩展,其表示在 JS 代码中编写 HTML 模版结构,它是 React 中编写 UI 模版的方式。
在 React 中,className
是用来指定 HTML 元素的 CSS 类的属性。由于 JavaScript 语言中 class
是一个保留字,因此在 JSX 中使用 class
时会出现语法错误。因此,React 使用 className
来替代 class
。
可以使用在线网站查看 jsx 如何被 babel 转为 js 语法:https://www.babeljs.io
点击网站上方导航栏的 try it out 将 react 勾选上就可以简单看看:
JSX 中使用 JS 表达式
在 JSX 中可以通过大括号 {}
识别 JavaScript 中的表达式,比如常见的变量、函数调用、方法调用等等。常见场景有:
- 使用引号传递字符串
- 使用 JavaScript 变量
- 函数调用和方法调用
- 使用 JavaScript 对象
其中 <div style={{ color: 'blue' }}>早上好,夜之城。</div>
的 style 的第一层大括号是识别 JS 表达式,用于识别包含 color 属性的对象。
warning
if 语句、switch 语句、变量声明等属于语句,不是表达式,不能出现在大括号 {} 中
JSX 实现列表渲染
渲染需求:
在 JSX 中可以使用原生 JS 的 map 方法变量渲染:
warning
key
应当是独一无二的,该值供 React 内部使用,提升列表更新性能
JSX 条件渲染
语法:在 React 中,可以通过逻辑与运算符 &&
、三元表达式 (?:)
实现基础的条件渲染
- 逻辑与:
{flag && <span>flag为真则显示span</span>}
- 三元表达式:
{loading ? <span>loading...</span> : <span>show</span>}
复杂条件下,可以使用自定义函数返回不同结果的 jsx 模板来实现需求。
React 基础事件绑定
基础绑定
语法:on + 事件名称 ={事件处理程序},整体上遵循驼峰命名法(如:handleClick)
获取事件
语法:在事件回调函数中写上形参 e
传递自定义参数
语法:在绑定事件中改写为箭头函数写法,将实参传递
传递自定义参数和事件
warning
请注意自定义参数和事件参数 e 的位置。传递和接收须一一对应。
React 组件
在 React 中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图 UI,渲染组件只需要把组件当成标签书写即可。
warning
请注意声明和使用时,组件名首字母大写。
useState 基础使用
usestate 是 - 一个 ReactHook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果。
本质:和普通 JS 变量不同的是,状态变量一旦发生变化组件的视图 UI 也会跟着变化(数据驱动视图)。
例如:const [count, setCount] = useState(0)
- useState 是一个函数,返回值是一个数组。
- 数组中的第一个参数是状态变量,第二个参数是 set 函数用来修改状态变量
- useState 的参数将作为 count 的初始值
简单案例:
warning
- 请先将 useState 进行导入
- 请注意使用 setCount 时,传递的值为新值,不能使用
count++
或者 count += 1
等类似写法。
修改状态的规则
前文提到,使用 setCount 时,传递的值为新值,不能使用 count++
或者 count += 1
等类似写法。
info
在 React 中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新。
即,你不能使用以下方式更改 count 的值:
于是,修改对象这种复杂类型的状态变量,也同样适用。
复杂类型修改
info
在 React 中,对于对象类型的状态变量,应该始终传给 set 方法一个全新的对象来进行修改。
例如:
其中的 setUser 方法将属性展开,再书写要覆盖的属性进行赋值,这样就完成了实际上的属性修改,如果不展开,那么属性将只剩下后书写的属性。
React 样式控制
一般分为行内写法和分离写法。
行内写法例如:
或者像前面的写法一样,写成:<span style = {{ color: red }}>
分离写法的意思是,将样式抽离为一个 css 文件进行导入使用。具体是在标签上使用 className
(此为固定名)进行导入使用。例如:
classnames 类名控制
github 地址:https://github.com/JedWatson/classnames
可以使用 classnames 进行类名控制,解决类名控制时使用模板字符串拼接类名过多时的拼接易出错、不直观的问题。
例如这种类名写法:
如果使用 classnames 则为:
React 表单受控绑定
概念:使用 React 组件的状态(useState)控制表单的状态。
就是使用 useState 的 value 绑定输入框的 value,再通过 onChange 事件将输入的值传给 set 方法,实现输入数据视图更新的操作。例如:
一旦输入框输入值,span 标签将会显示值。
React 获取 Dom
在 React 组件中获取 / 操作 DOM,需要使用 useRef 钩子函数:
- 使用 useRef 创建 ref 对象,并与 JSX 绑定
- 在 DOM 可用时,通过 inputRef.current 拿到 DOM 对象
React 父子通信
父传子
- 子组件标签上通过属性绑定数据
- 子组件通过 props 形参接收数据对象
请注意子组件中使用的是 appName 属性,而非具体传递的 name 变量。
warning
props 可以传递任意合法的 JS 数据,但 props 是只读的,子组件不能直接修改该数据。
特殊的 props 属性 - children
如果不是在子组件的标签属性中传递数据,而是将内容嵌套在子组件标签中,则子组件将通过一个默认的 props 属性进行接收,该属性名为 children。
子传父
核心思想:利用父组件传递的函数传递自己的参数 / 数据
如果传递的函数数量较少,也可以直接解构处理它。
React 兄弟通信
很容易想到一个办法是:借助父组件进行数据传递。
朴素的办法 - 状态提升
假设有 B、C 组件为兄弟关系,它们存在一个共同的父组件 App,构建如下:
注意区分给 B 组件和 C 组件传递的 AppMsg 分别是什么:B 为回调函数,C 为来自 B 组件的消息字符串。
运行结果如下:
其他方法 - 待补充
TODO
跨层级通信 - Context
适用于形成顶层 - 底层层级关系的组件通信。
假设有 App、B、C 三个组件。B 组件是被 App 使用的子组件,且 B 组件中又使用 C 组件,则此时 C 组件为底层组件而 App 为顶层组件。
- 使用 createContext 方法创建一个上下文对象 Ctx(名称可随意)
- 在顶层组件(App)中通过
Ctx.Provider
组件提供数据 - 在底层组件(C)中通过
useContext
钩子函数获取消费数据
代码示例如下:
传递多个数据 <MsgContext.Provider value={[AppMsg, AppMsg2]}>
,需要用数组进行传递。
实际上,任何能够形成顶层 - 底层层级的组件关系都可以使用 Context 进行通信。
例如,父子关系也是一种顶层 - 底层关系。
useEffect 概念
useEffect 是一个 React Hook
函数,用于在 React 组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送 AJAX 请求,更改 DOM 等等。
一个简单的场景是:当组件加载完毕之后需要获取数据(发送请求),实现数据渲染。
语法:
- 参数 1 是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作。
- 参数 2 是一个数组(可选),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次。
代码可以是:
依赖项的具体影响如下表:
依赖项 | 副作用函数执行时机 |
---|
没有依赖项:不写 | 组件初始渲染 + 组件更新时执行 |
空数组依赖:[ ] | 只在初始渲染时执行一次 |
特定依赖项:[example] | 组件初始渲染 + 特定依赖项变化时执行 |
useEffect - 清除副作用
在 useEffect 中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作。比如在 useEffect 中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用。
语法:
也就是在 return 中新写一个函数,该函数最常见的执行时机是在组件卸载时自动执行。
例如,在 Son 组件中开启一个定时器,卸载时清除这个定时器。代码可以是:
React 自定义 hook 函数
假设要实现一个将某元素结构隐藏 / 显示的按钮逻辑,代码可以是:
但很显然,这种切换元素结构隐藏或显示的代码是经常使用的,如果每一次都需要手动再写一次较为麻烦,于是这里引出封装自定义 hook 函数。
- 声明一个以 use 打头的函数
- 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
- 把组件中用到的状态或者回调 return 出去(以对象或者数组)
- 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
所以,代码也可以是下面这种:
warning
- hook 函数只能在组件中或者其他自定义 hook 函数中调用
- hook 函数只能在组件的顶层调用,不能嵌套在 if、for 或者其他函数中
下面两种调用方式都是错误的:
组件外调用
嵌套在 if 语句中
Redux-html 小试牛刀
Redux 是 React 最常用的集中状态管理工具,类似于 Vue 中的 Pinia(或者 Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
使用步骤:
- 定义一个 reducer 函数(根据当前想要做的修改返回一个新的状态)
- 使用 createStore 方法传入 reducer 函数生成一个 store 实例对象
- 使用 store 实例的 subscribe 方法订阅数据的变化(数据一旦变化,可以得到通知)
- 使用 store 实例的 dispatch 方法提交 action 对象触发数据变化(告诉 reducer 你想怎么改数据)
- 使用 store 实例的 getState 方法获取最新的状态数据更新到视图中
例如在 Html 中使用 redux,代码如下:
Redux 与 React
在 React 中使用 redux,官方要求安装俩个其他插件:Redux Toolkit 和 react-redux
- Redux Toolkit(RTK)是官方推荐编写 Redux 逻辑的方式,是一套工具的集合集,简化书写方式
- react-redux 是用来链接 Redux 和 React 组件的中间件
安装命令可以是:
或者使用 vite 构建项目,命令可以是:
运行上述命令之后,按照提示选择 create-react-app 模板即可(SWC 是 JavaScript 编译工具比 Babel 要快,但是在功能和插件生态系统方面 Babel 更完善)。
Redux 目录结构安排
安装好 Redux 后,应该对 sotre 目录结构有一定的安排,可以是:
index.js 将 modules 中的 store 导出即可,比较类似 Pinia 的目录安排。
创建一个 counterSotre
编写代码如下:
info
- 为什么创建 Store 的方法为 createSlice?
因为在 Redux 中,"切片"(slice)是指将 Redux 状态的逻辑按照功能进行分割和组织的方式。每个切片通常包含以下几个部分:
- 初始状态:定义该切片的初始状态,通常是一个对象。
- reducers:一组用于更新该切片状态的函数。每个 reducer 函数定义了如何根据不同的 action 修改状态。
- actions:由 reducers 生成的操作,这些操作可以在组件中被触发,以更新状态。
切片的主要目的是提高代码的可维护性和可读性,将不同功能的状态和行为组织到一个地方,避免将所有的状态和逻辑集中在一个大型文件中。这样可以使状态管理更加清晰和模块化。
导出 store
在 store 目录下的 index.js 文件中编写代码如下:
由于子模块导出时,使用的是 export default
方式,所以可以重新命名为 counterReducer 以供 reducer 管理。此处的 reducer 和子模块的 reducer 类似一群孩子和监护人的关系,各个子模块的 reducer 由此处 index.js 的 reducer 管理。
Store 注入 React
react-redux 负责把 Redux 和 React 链接起来,内置 Provider 组件通过 store 参数把创建好的 store 实例注入到应用,
链接正式建立。
在由 Vite 创建的项目中,找到 src\main.jsx
文件(如果是 npm 创建则是 index.js):
使用 Provide 将 App 包裹,并指定好 store 属性,就可以在 React 中使用 redux 了。
在组件中使用 store
找到 App.jsx 修改为如下代码:
这个地方的 state 参数 state => state.counter
指的其实是 sotre 的 index.js 文件中的 reducer 中的 counter:
不过现在只能显示数据,而不能修改。
修改数据
React 组件中修改 store 中的数据需要借助另外一个 hook 函数:useDispatch
它的作用是生成提交 action 对象的 dispatch 函数。
App.jsx
在这段代码中,使用 dispatch
方法并传入 increment()
时,它实际上是调用导入了的 increment
这个 action creator 函数,这个函数会返回一个 action 对象。例如:
异步状态操作