区分命名类似的React API与Hooks
Memo
useMemo()
(hook):缓存计算的结果;也可模拟useCallback;React.memo(FC, checkPropsEqualFn)
:为函数式组件添加浅比较缓存功能,对标继承了PureComponent的类组件;
以上两者均提供缓存功能以减少不必要的重渲染,提升性能;
Ref
useRef()
(hook):在多次渲染之间共享数据;也可以存储组件实例引用;React.createRef()
:获得组件实例的引用;
使用 useRef 保存的数据一般是和 UI 的渲染无关的,值的变更不会触发重渲染;
当以上两者的值被作为组件ref属性时可以存储组件实例引用(useRef平常要.current才能存取,这种情况下直接就存值了)
- 一个关于useRef 有趣的代码片段
import { useRef } from 'react';
// 创建一个自定义 Hook 用于执行一次性代码
function useSingleton(callback) {
// 用一个 called ref 标记 callback 是否执行过
const called = useRef(false);
// 如果已经执行过,则直接返回
if (called.current) return;
// 第一次调用时直接执行
callBack();
// 设置标记为已执行过
called.current = true;
}
const MyComp = () => {
// 使用自定义 Hook
useSingleton(() => {
console.log('这段代码只执行一次');
});
return (<div>My Component</div>);
};
与useEffect(cb, [])的区别在于上述自定义hook是在函数体执行最初执行,而useEffect则是在render之后执行
Context
React.createContext()
:
返回的对象可取出Ctx.Provider
作为容器组件<Ctx.Provider value = { ctxVal }>
取出Ctx.Consumer
作为消费者组件(此时要使用 “函数作为子元素” 模式<Ctx.Consumer>{ ctxVal => {/* 子组件可访问ctxVal */}}</Ctx.Consumer>
);也可以通过设置类组件的contextType为此对象 使组件render时可访问this.context对象,但是这意味着该组件只能使用这唯一的一个context 除非包裹多层绑定了不同context的父组件……挺麻烦的;useContext()
(hook):
在Ctx.Provider
包裹的后代函数式组件中可以把共享的value直接拿到,而不需要Ctx.Consumer + 函数子元素
的模式;
二者均提供了一个变更时会触发重渲染的全局变量,但全局变量意味着:1.让调试变得困难,很难跟踪某个 Context 的变化究竟是如何产生的。2. 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上。
“函数作为子元素” 模式在RenderProps也有使用:
// Parent的render属性可理解为一个FC,可返回JSX,因此在HOC中可直接调用
<Parent render = { enhanceValue => <Sub val={enhanceValue}/> }/>
// HOC内:此处使用了函数子元素
return(<>{ this.props.render(this.state.enhanceValue) }</>)
Redux Hooks
把状态放在组件之外,就可以让 React 组件成为更加纯粹的UI层,很多对于业务数据和状态数据的管理,就都可以在组件之外去完成;
同时也天然提供了状态共享的能力,两个场景:
- 跨组件的状态共享:当某个组件发起一个请求时,将某个 Loading 的数据状态设为 True,另一个全局状态组件则显示 Loading 的状态。
- 同组件多个实例的状态共享:某个页面组件初次加载时,会发送请求拿回了一个数据,切换到另外一个页面后又返回。这时数据已经存在,无需重新加载。设想如果是本地的组件 state,那么组件销毁后重新创建,state 也会被重置,就还需要重新获取数据。
react-redux 的实现利用了 React 的 Context 机制去存放 Store,但是它没有Ctx.Provider的Consumer使用起来那么麻烦
import { Provider } from 'react-redux'
<Provider store={store}>
<App />
</Provider>
react-redux 还提供 useSelector
和 useDispatch
两个 Hooks,比起原来connect时所使用的两个那么长的API,hooks使用简单,命名也更好懂,实在是很舒服 :
如果出于某种原因,如单元测试,想要获取不同的store,我们可以使用useStore()这个hook并通过新的contextAPI传递进组件树中,就像下面这样:
import React from 'react';
import { useStore } from 'react-redux';
import OtherProvider from './OtherProvider';
const Component = props => {
const store = useStore();
return <OtherProvider store={store}> {props.children} </OtherProvider>
}
Hooks不能完全替代生命周期
Class 组件中还有其它一些比较少用的方法,比如 getSnapshotBeforeUpdate
, componentDidCatch
, getDerivedStateFromError
。
目前 Hooks 还没法实现这些功能,如果必须用到,仍然需要用Class 组件去实现。
不过社区已经存在基于Hooks的解决方案来处理异常:
https://juejin.cn/post/6974383324148006926#heading-14