用hooks模拟 React 生命周期

4,247 阅读2分钟

使用hooks有一段时间了,之前也针对自己的使用经验整理了一下 react hooks 实践总结,现在对最后那个 flag 也整理一下

首先说一下 react 的几个阶段

  • 挂载
    • constructor
    • static getDerivedStateFromProps
    • UNSAFE_componentWillMount 后期会被废弃
    • render
    • componentDidMount
  • 更新
    • static getDerivedStateFromProps
    • UNSAFE_componentWillReceiveProps 后期会被废弃
    • UNSAFE_componentWillUpdate 后期会被废弃
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载
    • componentWillUnmount

挂载

constructor

这个构造函数没法模拟

getDerivedStateFromProps

会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。 它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

// 这里注意到其实这个state并不是真实的state,而是一个跟props相关的对象
const useGetDeriveStateFromProps = (state, props, handle) => {
  const cacheState = useRef(state);
  const newState = handle(cacheState.current, props);
  if (newState) {
    cacheState.current = newState;
  }

  return cacheState.current;
};

// 使用

const Component = props => {
  const state = useGetDeriveStateFromProps({ x: 1 }, props, (state, props) => {
    console.log('new getDerivedStateFromProps')
    if (props.add) {
      state.x += 1;
      return state;
    }
    return null;
  });
  
  return <div>{state.x}</div>;
};

UNSAFE_componentWillMount

在 render 之前执行,且执行一次

const useWillMount = handle => {
  const isFirstLoad = useRef(true);
  if (isFirstLoad.current) {
    handle();
    isFirstLoad.current = false;
  }
};

componentDidMount

会在组件挂载后(插入 DOM 树中)立即调用,且整个生命周期中只会调用一次

const useDidMount = handle => {
  useEffect(handle, []);
};

更新

getDerivedStateFromProps

上面已经说过了

UNSAFE_componentWillReceiveProps

当 props 改变的时候触发,且在 render 之前

const useWillReceiveProps = (props, handle) => {
  const cacheProps = useRef(props);
  const isFirst = useRef(true);
  if (isFirst.current) {
    isFirst.current = false;
    return;
  }
  if (cacheProps.current !== props) {
    handle();
    cacheProps.current = props;
  }
};

UNSAFE_componentWillUpdate

当组件收到新的 props 或 state 时,会在渲染之前调用 UNSAFE_componentWillUpdate()。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。每次render前都会调用

const useWillUpdate = handle => {
  const isFirst = useRef(true);
  if (isFirst.current) {
    isFirst.current = false;
    return;
  }
  handle();
};

shouldComponentUpdate

根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。 这里可以用 React.memo 去优化函数组件的,且useState是会进行浅比较state\

getSnapshotBeforeUpdate

在最近一次渲染输出(提交到 DOM 节点)之前调用。 它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。 此生命周期的任何返回值将作为参数传递给 componentDidUpdate

这里有点难解决,可以考虑讲整个生命周期抽象出来

componentDidUpdate

会在更新后会被立即调用。首次渲染不会执行此方法。

// 注意这里漏掉了 snapshot,也就是 getSnapshotBeforeUpdate 的返回值
const useDidUpdate = (props, states, handle) => {
  const isFirst = useRef(true);
  const cacheHandle = useRef(handle);
  const cache = useRef({ props, states });
  cacheHandle.current = handle;
  useEffect(() => {
    if (isFirst.current) {
      isFirst.current = false;
      return;
    }
    cacheHandle.current(cache.props, cache.state);
    cache.current = { props, states };
  });
};

卸载

componentWillUnmount

会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

const useWillUnmount = handle => {
  const _handle = useRef();
  _handle.current = handle;
  useEffect(() => _handle.current, []);
};

setState 模拟

const useSetState = (state) => {
  const [data, setData] = useState(state);
  const setState = (newState) => setData((state) => ({ ...state, ...newState}))
  return [data, setState];
}