# 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; // ... };