Class 生命周期函数

image.png

  • constructor

类组件的构造函数, 在v15版本🈶️构造函数传入setState和upate对象,v16.8中组件被实例化后手动赋值updater 对象,用于发起更新

  • WillMount、UNSAFE_WillMount

在render之前调用,v15版本中,该函数发出的setState不会发起更新而是直接赋值最新的state。v16.8中的setState,由Object.assign合并。 不会触发重新渲染

  • static getDerivedStateFromProps

该方法会在每次调用render函数之前调用,首次挂载和更新。可以返回一个派生的state,接受下一个props和上一个state。 返回的结果会被重新赋值给state

  1. // v16.8 对getDerivedStateFromProps的处理
  2. var partialState = getDerivedStateFromProps(nextProps, prevState);
  3. var memoizedState = partialState === null
  4. || partialState === undefined
  5. ? prevState
  6. : _assign({}, prevState, partialState);
  7. workInProgress.memoizedState = memoizedState;
  • render

是类组件必须实现的方法,可以返回JSX元素、数组、fragments、Portals、字符串、数字、boolean、null、undefined。

  • DidMount

在组件插入到真实dom之后调用,依赖与dom节点的初始化应该方法应该放在这里,或做一些副作用操作。此时的setState会触发重新渲染。v15中的setState会在上一个事物的close阶段执行更新,v16.8中会类似,会在调度开始阶段刷新work

  • willReceiveProps、UNSAFE_WillReceiveProps

在组件接收到新的props之后调用,可以比较currentProps、nextProps 完成setState操作。 如果父组件更新导致的重新渲染,即使props没有变更也会重新执行

  • shouldComponentUpdate

可以比较前后两个组件状态来决定是否参与本次更新。 继承了PureComponent 被默认实现, v15中如果不需要更新则重新赋值最新的props和state, 需要更新就卸载、重新挂载组件。

  • getSnapshotBeforeUpdate

接受props和state, 在最近一次提交的dom之前调用,可以在组件发生变化之前从dom中捕获信息。

该方法的返回值将作为DidUpdate 函数的第三个参数”snapshot”

  • WillUpate -> UNSAFE_WillUpdate

该方法会在组件收到新的props或者state时,重新render 之前调用。 该方法的返回值作为参数传递给DidUpdate

DidUpdate方法返回之前不应该触发对组件的更新

  • DidUpdate

在重新render 之后立即调用,可以对比前后props,重新调用

  • WillUnmount

组件卸载之前调用,可以取消订阅

  • static getDerivedStateFromError

此函数在后代组件抛出错误时被调用。将错误作为参数,并返回一个值更新state

  • DidCatch

在后代组件抛出错误后被调用

有用吗?

Hooks

  • useState

组件存储的state函数。 接受一个初始值或create函数,返回对应的值,和配套的setState

对应的setState同样可以传入函数。 该hook不会参与后续更新,只能手动发起

  • useEffect

在初始化时调用create函数,卸载时调用destroy 函数。 在每次依赖项更新时,首先调用上一次的销毁函数,再调用最新的创建回调函数。

在react等待浏览器完成画面渲染之后异步的调用该函数

  • useLayoutEffect

与effect类似, 不同的是在所有dom变成以后同步的调用该hook回调,和setState的第二个参数一致。
可以读取dom布局,并同步触发重新渲染的场景。在每一次dom重新变更到页面以后同步到执行。

ssr时差别很大

  • useContext

上下文管理,接受一个context对象。 并返回context的最近提供的value值。

组件最近的provider更新时,该hook所在组件会重新渲染。并使用最新的value值

组件存在该hook时,即使使用了memo包裹也会被穿透更新

  • useRecucer

useState的替代方案增强版。类似于redux的工作流程

有时候初始值依赖于props, 因此调用hook时指定
(state = initValue)的方式可以被替代,第二个参数为undefined 即可。

第三个参数是一个值或者函数返回一个值,会作为state的初始值

  1. function init(initialCount) {
  2. console.log(initialCount); // -> 10
  3. return { count: initialCount + 10 };
  4. }
  5. function reducer(state, action) {
  6. switch (action.type) {
  7. case "increment":
  8. return { count: state.count + 1 };
  9. case "decrement":
  10. return { count: state.count - 1 };
  11. case "reset":
  12. return init(action.payload);
  13. default:
  14. throw new Error();
  15. }
  16. }
  17. function APP({ initCount }) { // -> 10
  18. const [state, dispatch] = useReducer(reducer, initCount, init);
  19. return (
  20. <>
  21. Count: {state.count} // -> 20
  22. <button
  23. onClick={() => dispatch({ type: "reset", payload: initCount })}
  24. >
  25. Reset
  26. </button>
  27. <button onClick={() => dispatch({ type: "decrement" })}>-</button>
  28. <button onClick={() => dispatch({ type: "increment" })}>+</button>
  29. </>
  30. );
  31. }
  • useCallback

缓存一个函数

  1. function Counter() {
  2. const [count, setCount] = useState(0);
  3. return (
  4. <>
  5. Count: {count}
  6. <button onClick={() => setCount(c => c + 1 )}>-</button>
  7. </>
  8. );
  9. }
  10. function Counter() {
  11. const [count, setCount] = useState(0);
  12. const exec = useCallback(() => {
  13. setCount(count + 1);
  14. }, [count]);
  15. return (
  16. <>
  17. Count: {count}
  18. <button onClick={exec}>-</button>
  19. </>
  20. );
  21. }

在依赖项更新时,回调函数会重新创建,参数会变化。函数所在上下文会更新

在父子组件使用时可以使用,父组件发起的此次更新不会影响子组件,如果没有使用子组件依然会更新。被包裹之后可以避免这种情况

  • useMemo

缓存一个值,在依赖项变化的时候会重新执行回调,重新计算。可以仅在需要时选择更新

该hook在依赖项变更时会参与重新渲染,与effect不同的是,memo在执行dom变更前执行,effect是变更后异步的执行

  • useRef

换成一个不可变的数据。在整个组件存活期内,ref不被手动变更就不会发生变化。

可以用来保存dom实例,或者存放唯一实时的具体值,比如Stack

  • useImperativeHande

可以在子组件暴露一个值给父组件使用,配合forwardRef函数使用,接受一个ref对象

  1. const Child = forwardRef((props, ref) => {
  2. useImperativeHandle(ref, () => ({
  3. focus() {
  4. console.log("组件获取焦点");
  5. }
  6. }))
  7. return (
  8. <div>
  9. xxx
  10. </div>
  11. )
  12. })
  13. function App() {
  14. const childRef = useRef({});
  15. return <div>
  16. <button onClick={() => childRef.current?.focus()} >获取焦点</button>
  17. <Child ref={childRef} />
  18. </div>
  19. }

也没用

State & Props

state是一个组件在存活期间所依赖的状态数据,可以作为props给到子组件。props是父组件给到参数,可以是你能想到能使用的值。

父组件的state作为props传递下去, 子组件的props变更在对其他兄弟子组件不存在影响时可以派生到state

state 由组件自己维护,可以存在变更。props是只读的可以派生,但是在修改时应该在数据源处提交变更,也就时props的回调

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

不要直接修改state, 要使用react提供的更新函数,比如类组件和函数组件的setState

state 的更新可能时异步的, state的更新会被浅合并, state 数据是向下流动的

组件有状态和无状态是属于组件实现的细节,可能在有状态组件中使用无状态组件

Protals

react-dom 提供的方法,可以将子节点渲染到存在父节点以外的dom节点下的方案

第一个参数为child可以渲染任何有效元素,第二个参数为目标容器,第三个为key

protal可以被放置在dom树中的任何地方,但是其行为和react子节点行为一致,protal仍存在在react树,且和dom树中的位置无关,state和props仍然以jsx树为准

  1. const protals = document.getElementById("protals");
  2. const Child = ({count,setCount}) => {
  3. return (
  4. <div onClick={() => setCount(v => v+1)}>
  5. {count}
  6. </div>
  7. )
  8. }
  9. function App() {
  10. const [count, setCount] = useState(0)
  11. return createPortal(<Child setCount={setCount} count={count} />, protals)
  12. }

协调

每次调用render方法会创建一棵由react元素组成的树,在下一次state或者props变更时,相同的render方法返不同的树,需要基于两棵树之间的差别来判断如何高效的更新UI,保证jsx树和真实ui的同步

一个树转换成另一棵树的最小操作次数一些优化算法复杂度也是O(n**3). 随着树的节点增加,n也增加

在以下两个基础上提出了一个O(n)的启发式算法

  1. 两个不同类型的元素会产生不同的树
  2. 开发这可以通过设置key属性,来告知渲染哪些子元素在不同的渲染下可以保存不变

所以某些情况下,性能会有所消耗

  1. 如果类型不同,但内容相同,建议改成类型一致的元素
  2. key应该具有稳定性,可预测,以及列表内唯一的特质(要不然报警告😊)。 不稳定的key会导致许多组件实例和dom节点被不必要地重新创建。可能导致性能下降和子组件中状态丢失