# hooks 给 react 生态带来了什么变化

  • 不再需要类组件,推动了函数式编程

  • 代码复用

  • 简化的状态和副作用管理

  • 简化了组件的生命周期

# usecallback 的用处

  • 缓存函数

  • 函数需要传给子组件的场景下,可以避免子组件渲染

# ref 的用法

  • 基于 useRef()、React.createRef() 创建

  • 直接操作 dom 元素

  • 可以获取组件实例,从而调用实例方法、获取实例属性

  • 创建一个不被渲染影响的对象,且变更也不会导致渲染;由于这个特性可以保存上一次渲染的状态,可以得到渲染原因

# useEFfect 与 useLayoutEFfect 的区别

  • useLayoutEFfect,完成布局之后开始绘制之前触发(同步执行),会阻塞浏览器的绘制,确保在绘制之前完成了所有的 dom 操作

    • 会阻塞渲染,所以适合 操作 dom、影响布局,如:修改 dom 布局、获取元素尺寸、位置
  • useEffect,完成布局和绘制之后触发(异步执行),不会阻塞浏览器绘制,允许页面在副作用执行之前将页面先显示给用户

    • 不会影响页面的首次绘制,所以适合做数据请求、事件监听

# useMemo 的返回值

  • 返回的是一个被缓存的值,如果依赖项不变,那么不会执行重新计算,而是返回缓存值

# useMemo 的应用场景

  • 计算量大的操作,用 useMemo 缓存,避免每次渲染都要重新计算

  • 可以避免不必要的重新渲染,比如组件的依赖项是对象或者数组的时候,用 useMemo 缓存一下

# 函数式组件如何模拟生命周期钩子

  • useEffect [] 可以模拟 componentDidMount

  • useEffect [key] 可以模拟 componentDidUpdate

  • useEffect [] 设置 return 可以模拟 componentWillUnMount

  • React.memo 浅比较 props 可以模拟 shouldComponentUpdate

# 封装过的 hooks

  • window.addEventListener('resize', handleResize); 监听窗口变更,获取指定 dom 元素的 offsetHeight

  • new URLSearchParams(window.location.serach) 获取路由?后的参数

  • window.matchMedia('(prefers-color-scheme: dark)') 媒体查询监听浏览器模式改变

# 函数组件和 class 组件区别

  • 语法不同,函数组件 没有 this,接受 props 返回 jsx 结构;class 组件,有 this,通过 this 访问 props 和 state,需要定义 render()方法

  • 状态管理,函数组件依赖 hooks 和 useState;class 组件靠 this.state 和 this.setState()

  • 生命周期,函数组件靠 hooks;class 组件有生命周期

  • 性能优化,函数组件依靠 memo;class 组件靠 PureConponent

# react 的父子兄组件的渲染顺序

  • 初次渲染(mount)的渲染顺序

    • 从父组件开始,递归往下渲染子组件;如果有多个子组件,按照 jsx 的顺序依次渲染
  • 组件更新(rerender)的渲染顺序

    • 父组件的 render 方法首先执行,重新渲染父组件的 jsx 内容

    • 按声明顺序重新渲染子组件

    • 如果子组件的属性或者状态没有改变,那么可能会跳过子组件的渲染(需要依赖 memo PureComponent shouldComponentUpdate 等优化手段)

  • 声明周期函数的调用顺序

    • 父 shouldComponentUpdate

    • 父 render

    • 子 shouldComponentUpdate

    • 子 render

    • 子 componentDidUpdate

    • 父 componentDidUpdate

  • 函数组件的调用顺序

    • 父 render

    • 子 render

    • 子 useLayoutEffect

    • 父 useLayoutEffect

    • 子 useEffect

    • 父 useEffect

# redux 的底层运行机制

  • redux 是一个状态管理工具,核心思想是将应用的状态存储在一个全局的 store 里面,然后通过单向数据流和纯函数来管理状态

  • store 状态树,全局唯一,可以通过 getState()获取状态

  • action 动作,所有状态更新都是通过 dispatch(action) 发送 action 来触发的。action 包含 type 和 payloda 载荷

  • reducer 纯函数,根据传入的 action 来决定如何更改状态,接受当前状态和 action,返回新的状态

  • dispacth 派发,将 action 发给 reducer,根据 reducer 的返回结果更新状态

  • subscribe 订阅监听,当状态变化时候,redux 调用订阅的监听器函数,让应用响应状态的变化

  • 单向数据流,用户通过 dispatch 发出 action,store 接受到 action 并传给 reducer,reducer 根据 action 和当前状态返回新的状态,store 更新状态并通知视图层重新渲染

  • 中间件,在 action 发出和到达 reducer 的之前拦截,可以处理日志记录、异步操作之类的

# redux 适合场景

  • 复杂的状态管理。适合模块多,而且大量模块状态共享 的场景。

  • 跨组件的状态共享。避免了逐层传递 props。

  • 可预测的状态变化。store 由 reducer 和 action 完成更改,整个流程清晰可预测。

  • 缺点:不适合局部状态管理

# useContext 的底层运行机制

  • Contenx。可以将 状态或者功能 定义在 Contenx 中,子组件都可以访问这些数据,无需层层传递 props。

  • Provider。使用Contenx.Provider包裹组件树,并且提供一个 value 属性,这样子组件就可以通过 useContenx 访问这些值。

  • useContenx hook。消费 Contenx 内的数据

# useContext 适合场景

  • 简单状态管理

  • 局部状态共享

  • 优点:react 自带的 hook,不需要引入三方库;适合局部状态管理,也可以避免逐层传递 props

  • 缺点:任何的状态变化都会导致消费了该 contenx 的组件重新渲染

# redux 为什么不会引起消费了 store 的组件重新渲染

  • 使用 connect 高阶组件mapStateToProps 选择性订阅部分store,从而实现明确声明组件使用的那一部分 store 状态

  • connect 内部使用了浅比较,只有 props 的数据发生了变化才进行渲染

  • reducer,每个 reducer 只负责一部分的 store 状态,其他部分不受影响;reducer 是个纯函数,本身是没有状态的,他只负责他负责的那一部分的 store 更新成新的对象,从而使得 react 可以根据 store 的对象地址变化来确定哪部分需要更新

  • 可以继续选择性拆分 reducer,使得每个 reducer 可以变更的 store 更小

  • 可以使用 memo PureComponent 来避免不必要的 rerender

# useContext 为什么会引起消费其 contenx 的组件重新渲染,怎么优化

为什么会引起这个现象

  • 因为用法,如下,当 user、count 任何一个改变的时候,value 都会返回一个新的对象,所以整个 MyContenx.Provider 都重新渲染了; 而且 子组件使用 useContenx 消费 value 的时候,useContenx 不支持选择性订阅
const MyContenx = createContenx();

const Contanier = () => {
  const [user, setUser] = useState('');
  const [count, setCount] = useState(0);
  return (
    <MyContenx.Provider value={{ user, setUser, count, setCount }}>
      <ChildrenConponent />
    </MyContenx.Provider>
  );
};

优化方案

  • contenx 拆分

    • 子组件使用 memo 包裹,这样只针对 props 变更的场景才会触发 rerender,这一步是针对 props 进行的优化

    • 再将 Contenx 拆分,而且对应的 value 值用 useMemo,比如:将 user(因为 user 不会经常改变)的 contenx 提取到最外面,count 放在里面一层,这样的话 count 对象地址变更不会触发 user 的对象地址变更,从而不会导致其他 contenx 的 rerender; 使用 useMemo 的原因是其中一个 value 变更,其他的 contenx 的 value 地址不变,从而使得使用 useContenx 消费的值也不变,从而避免 rerender

    • 缺点:value 太多的情况下,拆分的工作量很大,维护起来很复杂

  • useReducer

    • 先看下 useReducer 的用法
    const MyContenx = createContenx()
    const initStae = { user: { name: 'xx' }, count: 0 }
    const reducer = (state, action) => {
      switch(action.type) {
        case 'setUser':
          return { ...state, user: action.payload }
        case 'setCount'
          return { ...state, count: action.payload }
        default:
          return state
      }
    }
    
    const ContainerEl = () => {
      const [state, dispatch] = useReducer(reducer, initState)
      const countValue = useMemo(() => ({ state.count, dispatch }), [state.count, dispatch])
      const userValue = useMemo(() => ({ state.user, dispatch }), [state.user, dispatch])
    
      return (
        <MyContenx.Provider value={{ countValue, userValue }}>
          <ChildrenConponent />
        </MyContenx.Provider>
      )
    }
    
    • 使用 useReducer 集中管理状态,每次更新值更新对应部分的 value;使用 useMemo 缓存不同类型的 value;使用 memo 优化子组件避免 props 引起不必要的 rerender

    • 这样 Provider 的 value 即使是一个内联对象,但是其内部对象的对象地址是 useMemo 包裹的,这样的话其子组件使用 useContenx 消费的时候就不会 rerender 了

  • selector 模式

    • 状态选择性订阅。selector 模式让组件只订阅它关心的状态片段,而不是整个 contenx 的 value;当某个片段的状态更新的时候,只有其依赖的组件 rerender,其他组件不受影响

    • 状态的深层次比较或自定义比较。selector 模式下可以对状态进行深层次比较(在自定义 hook 内进行),只有实际值变化,组件才会 rerender,这样可以避免是组件的引用地址变化了导致的 rerender

    • 用法。实际原理是:子组件通过自定义 hook,不再需要在子组件内部使用 useContenx(),这样就避免了其他 value 变化引起的 useContenx()变化,从而实现选择性订阅

    const MyContenx = createContenx();
    
    const ContanierEl = () => {
      const [count, setCount] = useState(0);
      const [user, setUser] = useState({ name: 'xx', age: 18 });
    
      return (
        <MyContenx.Provider value={{ user, setUser, count, setCoun }}>
          <ChildrenComponent />
        </MyContenx.Provider>
      );
    };
    
    // 定义自 selector hook
    const useCount = () => {
      const { count, setCount } = useContenx(MyContenx);
      return { count, setCount };
    };
    const useUser = () => {
      const { user, setUser } = useContenx(MyContenx);
      return { user, setUser };
    };
    
    // 子组件
    const ChildrenComponet = () => {
      const { count, setCount } = useCount;
      // ...
    };