We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
转载:dt-fe / weekly
React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。
然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说,稳定的规范比合理的规范更重要,因此这套方案只是最佳实践之一。
Function Component 采用 const + 箭头函数方式定义:
const
const App: React.FC<{ title: string }> = ({ title }) => { return React.useMemo(() => <div>{title}</div>, [title]); }; App.defaultProps = { title: 'Function Component' }
上面的例子包含了:
React.FC
React.useMemo
App.defaultProps
为什么不用 React.memo?
推荐使用 React.useMemo 而不是 React.memo,因为在组件通信时存在 React.useContext 的用法,这种用法会使所有用到的组件重渲染,只有 React.useMemo 能处理这种场景的按需渲染。
React.memo
React.useContext
没有性能问题的组件也要使用 useMemo 吗?
要,考虑未来维护这个组件的时候,随时可能会通过 useContext 等注入一些数据,这时候谁会想起来添加 useMemo 呢?
useContext
useMemo
为什么不用解构方式代替 defaultProps?
虽然解构方式书写 defaultProps 更优雅,但存在一个硬伤:对于对象类型每次 Rerender 时引用都会变化,这会带来性能问题,因此不要这么做。
defaultProps
局部状态有三种,根据常用程度依次排列: useState useRef useReducer 。
useState
useRef
useReducer
const [hide, setHide] = React.useState(false); const [name, setName] = React.useState('BI');
状态函数名要表意,尽量聚集在一起申明,方便查阅。
const dom = React.useRef(null);
useRef 尽量少用,大量 Mutable 的数据会影响代码的可维护性。
但对于不需重复初始化的对象推荐使用 useRef 存储,比如 new G2() 。
new G2()
局部状态不推荐使用 useReducer ,会导致函数内部状态过于复杂,难以阅读。 useReducer 建议在多组件间通信时,结合 useContext 一起使用。
可以在函数内直接申明普通常量或普通函数吗?
不可以,Function Component 每次渲染都会重新执行,常量推荐放到函数外层避免性能问题,函数推荐使用 useCallback 申明。
useCallback
所有 Function Component 内函数必须用 React.useCallback 包裹,以保证准确性与性能。
React.useCallback
const [hide, setHide] = React.useState(false); const handleClick = React.useCallback(() => { setHide(isHide => !isHide) }, [])
useCallback 第二个参数必须写,eslint-plugin-react-hooks 插件会自动填写依赖项。
发请求分为操作型发请求与渲染型发请求。
操作型发请求,作为回调函数:
return React.useMemo(() => { return ( <div onClick={requestService.addList} /> ) }, [requestService.addList])
渲染型发请求在 useAsync 中进行,比如刷新列表页,获取基础信息,或者进行搜索, 都可以抽象为依赖了某些变量,当这些变量变化时要重新取数 :
useAsync
const { loading, error, value } = useAsync(async () => { return requestService.freshList(id); }, [requestService.freshList, id]);
简单的组件间通信使用透传 Props 变量的方式,而频繁组件间通信使用 React.useContext 。
以一个复杂大组件为例,如果组件内部拆分了很多模块, 但需要共享很多内部状态 ,最佳实践如下:
export const StoreContext = React.createContext<{ state: State; dispatch: React.Dispatch<Action>; }>(null) export interface State {}; export interface Action { type: 'xxx' } | { type: 'yyy' }; export const initState: State = {}; export const reducer: React.Reducer<State, Action> = (state, action) => { switch (action.type) { default: return state; } };
import { StoreContext, reducer, initState } from './store' const AppProvider: React.FC = props => { const [state, dispatch] = React.useReducer(reducer, initState); return React.useMemo(() => ( <StoreContext.Provider value={{ state, dispatch }}> <App /> </StoreContext.Provider> ), [state, dispatch]) };
import { StoreContext } from './store' const app: React.FC = () => { const { state, dispatch } = React.useContext(StoreContext); return React.useMemo(() => ( <div>{state.name}</div> ), [state.name]) };
如上解决了 多个联系紧密组件模块间便捷共享状态的问题 ,但有时也会遇到需要共享根组件 Props 的问题,这种不可修改的状态不适合一并塞到 StoreContext 里,我们新建一个 PropsContext 注入根组件的 Props:
StoreContext
PropsContext
const PropsContext = React.createContext<Props>(null) const AppProvider: React.FC<Props> = props => { return React.useMemo(() => ( <PropsContext.Provider value={props}> <App /> </PropsContext.Provider> ), [props]) };
参考 react-redux hooks。
比如当输入框频繁输入时,为了保证页面流畅,我们会选择在 onChange 时进行 debounce 。然而在 Function Component 领域中,我们有更优雅的方式实现。
onChange
debounce
其实在 Input 组件 onChange 使用 debounce 有一个问题,就是当 Input 组件 受控 时, debounce 的值不能及时回填,导致甚至无法输入的问题。
我们站在 Function Component 思维模式下思考这个问题:
useDebounce
下面是一个性能很差的组件,引用了变化频繁的 text (这个 text 可能是 onChange 触发改变的),我们利用 useDebounce 将其变更的频率慢下来即可:
text
const App: React.FC = ({ text }) => { // 无论 text 变化多快,textDebounce 最多 1 秒修改一次 const textDebounce = useDebounce(text, 1000) return useMemo(() => { // 使用 textDebounce,但渲染速度很慢的一堆代码 }, [textDebounce]) };
使用 textDebounce 替代 text 可以将渲染频率控制在我们指定的范围内。
textDebounce
事实上,useEffect 是最为怪异的 Hook,也是最难使用的 Hook。比如下面这段代码:
useEffect
useEffect(() => { props.onChange(props.id) }, [props.onChange, props.id])
如果 id 变化,则调用 onChange。但如果上层代码并没有对 onChange 进行合理的封装,导致每次刷新引用都会变动,则会产生严重后果。我们假设父级代码是这么写的:
id
class App { render() { return <Child id={this.state.id} onChange={id => this.setState({ id })} /> } }
这样会导致死循环。虽然看上去 <App> 只是将更新 id 的时机交给了子元素 <Child>,但由于 onChange 函数在每次渲染时都会重新生成,因此引用总是在变化,就会出现一个无限死循环:
<App>
<Child>
新 onChange -> useEffect 依赖更新 -> props.onChange -> 父级重渲染 -> 新 onChange...
props.onChange
想要阻止这个循环的发生,只要改为 onChange={this.handleChange} 即可,useEffect 对外部依赖苛刻的要求,只有在整体项目都注意保持正确的引用时才能优雅生效。
onChange={this.handleChange}
然而被调用处代码怎么写并不受我们控制,这就导致了不规范的父元素可能导致 React Hooks 产生死循环。
因此在使用 useEffect 时要注意调试上下文,注意父级传递的参数引用是否正确,如果引用传递不正确,有两种做法:
useCurrentValue
function useCurrentValue<T>(value: T): React.RefObject<T> { const ref = React.useRef(null); ref.current = value; return ref; } const App: React.FC = ({ onChange }) => { const onChangeCurrent = useCurrentValue(onChange) };
onChangeCurrent 的引用保持不变,但每次都会指向最新的 props.onChange,从而可以规避这个问题。
onChangeCurrent
如果还有补充,欢迎在文末讨论。
如需了解 Function Component 或 Hooks 基础用法,可以参考往期精读:
讨论地址是:精读《React Hooks 最佳实践》 · Issue #202 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)
The text was updated successfully, but these errors were encountered:
No branches or pull requests
简介
React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。
然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说,稳定的规范比合理的规范更重要,因此这套方案只是最佳实践之一。
精读
环境要求
组件定义
Function Component 采用
const
+ 箭头函数方式定义:上面的例子包含了:
React.FC
申明 Function Component 组件类型与定义 Props 参数类型。React.useMemo
优化渲染性能。App.defaultProps
定义 Props 的默认值。FAQ
推荐使用
React.useMemo
而不是React.memo
,因为在组件通信时存在React.useContext
的用法,这种用法会使所有用到的组件重渲染,只有React.useMemo
能处理这种场景的按需渲染。要,考虑未来维护这个组件的时候,随时可能会通过
useContext
等注入一些数据,这时候谁会想起来添加useMemo
呢?虽然解构方式书写
defaultProps
更优雅,但存在一个硬伤:对于对象类型每次 Rerender 时引用都会变化,这会带来性能问题,因此不要这么做。局部状态
局部状态有三种,根据常用程度依次排列:
useState
useRef
useReducer
。useState
状态函数名要表意,尽量聚集在一起申明,方便查阅。
useRef
useRef
尽量少用,大量 Mutable 的数据会影响代码的可维护性。但对于不需重复初始化的对象推荐使用
useRef
存储,比如new G2()
。useReducer
局部状态不推荐使用
useReducer
,会导致函数内部状态过于复杂,难以阅读。useReducer
建议在多组件间通信时,结合useContext
一起使用。FAQ
不可以,Function Component 每次渲染都会重新执行,常量推荐放到函数外层避免性能问题,函数推荐使用
useCallback
申明。函数
所有 Function Component 内函数必须用
React.useCallback
包裹,以保证准确性与性能。useCallback
第二个参数必须写,eslint-plugin-react-hooks 插件会自动填写依赖项。发请求
发请求分为操作型发请求与渲染型发请求。
操作型发请求
操作型发请求,作为回调函数:
渲染型发请求
渲染型发请求在
useAsync
中进行,比如刷新列表页,获取基础信息,或者进行搜索, 都可以抽象为依赖了某些变量,当这些变量变化时要重新取数 :组件间通信
简单的组件间通信使用透传 Props 变量的方式,而频繁组件间通信使用
React.useContext
。以一个复杂大组件为例,如果组件内部拆分了很多模块, 但需要共享很多内部状态 ,最佳实践如下:
定义组件内共享状态 - store.ts
根组件注入共享状态 - main.ts
任意子组件访问/修改共享状态 - child.ts
如上解决了 多个联系紧密组件模块间便捷共享状态的问题 ,但有时也会遇到需要共享根组件 Props 的问题,这种不可修改的状态不适合一并塞到
StoreContext
里,我们新建一个PropsContext
注入根组件的 Props:结合项目数据流
参考 react-redux hooks。
debounce 优化
比如当输入框频繁输入时,为了保证页面流畅,我们会选择在
onChange
时进行debounce
。然而在 Function Component 领域中,我们有更优雅的方式实现。我们站在 Function Component 思维模式下思考这个问题:
onChange
本不慢,大部分使用值的组件也不慢,没有必要从onChange
源头开始就debounce
。useDebounce
。下面是一个性能很差的组件,引用了变化频繁的
text
(这个text
可能是onChange
触发改变的),我们利用useDebounce
将其变更的频率慢下来即可:使用
textDebounce
替代text
可以将渲染频率控制在我们指定的范围内。useEffect 注意事项
事实上,
useEffect
是最为怪异的 Hook,也是最难使用的 Hook。比如下面这段代码:如果
id
变化,则调用onChange
。但如果上层代码并没有对onChange
进行合理的封装,导致每次刷新引用都会变动,则会产生严重后果。我们假设父级代码是这么写的:这样会导致死循环。虽然看上去
<App>
只是将更新 id 的时机交给了子元素<Child>
,但由于onChange
函数在每次渲染时都会重新生成,因此引用总是在变化,就会出现一个无限死循环:新
onChange
->useEffect
依赖更新 ->props.onChange
-> 父级重渲染 -> 新onChange
...想要阻止这个循环的发生,只要改为
onChange={this.handleChange}
即可,useEffect
对外部依赖苛刻的要求,只有在整体项目都注意保持正确的引用时才能优雅生效。然而被调用处代码怎么写并不受我们控制,这就导致了不规范的父元素可能导致 React Hooks 产生死循环。
因此在使用
useEffect
时要注意调试上下文,注意父级传递的参数引用是否正确,如果引用传递不正确,有两种做法:useCurrentValue
对引用总是变化的 props 进行包装:onChangeCurrent
的引用保持不变,但每次都会指向最新的props.onChange
,从而可以规避这个问题。总结
如果还有补充,欢迎在文末讨论。
如需了解 Function Component 或 Hooks 基础用法,可以参考往期精读:
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
The text was updated successfully, but these errors were encountered: