从新版 React 文档中学到的
React 基础
-
JSX 大括号:获取全局变量的窗口
-
Props 是一个只读快照,每次 render 都接收一个新的 props 对象
-
Event handlers、useEffect 中执行副作用
-
3 类输入数据 props、state、context,把他们当成只读的
-
React 的严格模式,在开发时对每个组件调用两次 render,用来发现有副作用的组件,第二次调用的结果会被舍弃
-
组件更新的两个原因:调用 ReactDOM.render 触发的初始 render、state 更新 rerender
-
Rendering 就是 React 调用我们的组件,初始 render 是调用根组件,后续 render 是调用 state 更新的组件
-
Render function 需要是纯函数
-
将变化提交到 DOM,初始 render 是调用 appendChild、后续是最小更新集
-
React 会等待 event handlers 中的代码都执行完,才会处理状态更新
-
React 不会跨事件进行批处理,每个事件单独处理
State
基础
-
触发 state 更新的两类原因:用户输入(点击按钮、输入、跳转)、电脑输入(网络请求返回、计时器完成、图片加载完成)
-
用户输入一般需要 Event handler 处理
-
state 更新队列内部实现
export function getFinalState(baseState, queue) {
let finalState = baseState;
for (let update of queue) {
if (typeof update === 'function') {
// Apply the updater function.
finalState = update(finalState);
} else {
// Replace the next state.、
finalState = update;
}
}
return finalState;
}
复制代码
原则(方便更新、减少错误)
-
组合有关系的 state 变量:如果你总是同时更新一些 state 变量,考虑把他们组合成一个 state 变量;或者不知道 state 变量的数量时,比如表单数据
-
避免互斥的 state 变量:如果一些 state 变量之间互斥,就容易出现错误,避免这种情况
-
避免冗余的 state 变量:如果是可以根据现有 state 变量、props、url、cookie、localStorage 等计算出来的信息,就不需要放在 state 中;不会改变的值、props 传递的值不要放在 state 中
-
避免重复的 state 变量:如果相同信息在多个 state 变量间、url、storage 中重复,就很难去让他们保持同步。所以要避免重复的 state 变量
-
避免嵌套很深的 state 变量:嵌套过深的 state 变量不方便更新。所以最好保持 state 结果扁平
-
对于选中条目,使用 selectedId 或 selectedIndex,而不是选择条目这个对象
-
保证 state 变量的单一来源
使用
- 删除任何不必要的 state: 这个 state 是不是另一个 state 的子集;能不能从另一个 state 中获取同样信息
state 保留和重置
-
React 只会保留正在 UI 树中渲染的组件的 state。组件一旦从 UI 树中移除,React 就会销毁这个组件,丢弃他的状态
-
UI 树中同一个位置、同一个组件,React 会把他看作一个组件
-
同一位置不同组件,state 会被重置,这个组件的子树也会被销毁、重建
-
不应该嵌套定义组件函数,如果嵌套定义的话,每次外层 render,都会声明不同的组件,影响 React 判断。所以一定要在顶层声明组件函数
-
不让同一位置、同类型组件保留状态:让他们在不同位置、加 key
Reducer & Context
-
useReducer:reducer 函数中的 case 使用大括号并 return 回新的状态
-
reducer 的名字来源于 Array.reduce 的参数名,都叫 reducer,根据结果和当前数据返回下一个结果
-
reducer 是把 state 更新的逻辑单独抽离出来
-
reducer 应该是一个纯函数,不能有副作用
-
action 的名字应该是描述发生了什么
-
Context 就像 CSS 属性的继承,只能被更低层级 Provider 的值覆盖
-
Context 使用场景:主题、当前用户信息、路由、全局状态管理
-
Context 和 Reducer 一起使用,放到一个文件中,包括:
- reducer 及初始 state
- 创建 context 的逻辑
- 使用 children 作为 props 的 Provider 组件
- 导出 useSomeContext 函数
Ref
-
ref: 希望组件记住信息,又不希望这些信息变化后触发 rerender
-
ref 更新不会造成组件 rerender
-
如果信息用来 render,放在 state 中;如果信息只是 event handler 需要,且不会触发 render,使用 ref
-
不要在 rendering 时读写 ref.current,如果需要就使用 state。因为 React 感知不到 ref.current 的变化
-
ref 内部实现
// Inside of React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
复制代码
-
ref 使用场景,处理 React 之外的交互:存储 timeout IDs、存储操作 DOM 元素、存储不参与 JSX 计算的信息
-
ref 的变化会及时更新,和 state 每次 render 中的快照不同。因为 ref 就是普通的 JS 对象
-
ref callback 可以管理多个 refs
-
forwardRef 就是把父组件的 ref 属性传递给子组件
-
useImperativeHandle 让你可以自定义提供给父组件的 ref 对象
-
flushSync 强制 React 同步更新 DOM
Hooks
基础
-
Hooks 的本质就是提供了让函数组件能够绑定到某个可变的数据源的能力
-
每一次 UI 的变化,都是通过重新执行整个 Hooks 函数来完成的
-
Hooks 函数体中的代码,直接影响当次 render 的结果
-
Hooks 和普通函数的区别是普通函数中有没有用到其它 Hooks
内置 Hooks 解决了什么问题
-
useState 用于保存状态
-
useEffect 用于执行副作用
-
useCallback 用于缓存函数
-
useMemo 用于缓存计算结果
-
useRef 用于存储跨渲染的数据
-
useReducer 将状态更新逻辑抽离出来
具体 Hooks
useState
-
const [state, setState] = useState('')
setState() 的时候,re-render,整个 Hooks 函数重新执行 -
惰性初始化 state:初始化函数只在初始渲染时被调用,避免复杂计算性能开销
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
复制代码
useEffect:每次组件 render 完后判断依赖并执行相应的副作用
参数
-
useEffect 接收一个函数作为第一个参数,React 在 DOM 更新后调用这个函数
-
第一个参数不能是 async/await 函数,因为该语法默认返回一个 promise
-
第一个参数中,可以返回一个清除 effect 的回调函数,这个回调函数不只是会在组件销毁时执行,而且是每次 Effect 重新执行之前都会执行,用于清理上一次 Effect 的执行结果
-
useEffect 接受一个数组依赖项作为第二个参数,控制 effect 的条件执行
依赖项:避免执行无意义的工作
-
useEffect(() => {})
未声明第二个依赖项。每次 render 都执行 -
useEffect(() => {}, [])
声明一个空数组作为依赖项。仅第一次 render 后执行 -
useEffect(() => {}, [deps])
声明依赖项数组。第一次以及依赖项发生变化后执行 -
useEffect() => { return () => {} }, [])
声明依赖项数组,返回一个回调函数。仅第一次 render 后执行 effect,组件 unmount 后执行回调函数
useCallback
-
useCallback(fn, [deps])
只有当某个依赖变量发生变化时,才会重新声明 fn 这个回调函数 -
<CustomCompnent callback={fn} />
避免子组件的重复渲染
useMemo
useMemo(() => computeExpensiveValue(a, b), [a, b])
只有当某个依赖变量发生变化时,才会重新计算
useRef:
-
在多次渲染之间共享数据,存储跨渲染的数据
-
使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方
-
把变量定义到组件外,如果一个页面上有多个组件实例,组件外的普通变量是被共享的,可能会产生问题
其他
-
React 会使用浅比较来对比依赖项是否发生了变化,所以要特别注意数组或者对象类型。如果你是每次创建一个新对象,即使和之前的值是等价的,也会被认为是依赖项发生了变化。这是一个刚开始使用 Hooks 时很容易导致 Bug 的地方
-
写组件的顺序:简单项目从顶层到底层,复杂项目从底层到顶层