网站制作视频教程下载,班级优化大师官网,.net做网站的吗,什么网站可以用手机做兼职赚钱吗简介
React 16.8 于 2019.2 正式发布#xff0c;这是一个能提升代码质量和开发效率的特性#xff0c;笔者就抛砖引玉先列出一些实践点#xff0c;希望得到大家进一步讨论。
然而需要理解的是#xff0c;没有一个完美的最佳实践规范#xff0c;对一个高效团队来说#x…简介
React 16.8 于 2019.2 正式发布这是一个能提升代码质量和开发效率的特性笔者就抛砖引玉先列出一些实践点希望得到大家进一步讨论。
然而需要理解的是没有一个完美的最佳实践规范对一个高效团队来说稳定的规范比合理的规范更重要因此这套方案只是最佳实践之一。
精读
环境要求
拥有较为稳定且理解函数式编程的前端团队。开启 ESLint 插件eslint-plugin-react-hooks。
组件定义
Function Component 采用 const 箭头函数方式定义
const App: React.FC{ title: string } ({ title }) {return React.useMemo(() div{title}/div, [title]);
};App.defaultProps {title: Function Component
}上面的例子包含了
用 React.FC 申明 Function Component 组件类型与定义 Props 参数类型。用 React.useMemo 优化渲染性能。用 App.defaultProps 定义 Props 的默认值。
FAQ 为什么不用 React.memo? 推荐使用 React.useMemo 而不是 React.memo因为在组件通信时存在 React.useContext 的用法这种用法会使所有用到的组件重渲染只有 React.useMemo 能处理这种场景的按需渲染。 没有性能问题的组件也要使用 useMemo 吗 要考虑未来维护这个组件的时候随时可能会通过 useContext 等注入一些数据这时候谁会想起来添加 useMemo 呢 为什么不用解构方式代替 defaultProps? 虽然解构方式书写 defaultProps 更优雅但存在一个硬伤对于对象类型每次 Rerender 时引用都会变化这会带来性能问题因此不要这么做。
局部状态
局部状态有三种根据常用程度依次排列 useState useRef useReducer 。
useState
const [hide, setHide] React.useState(false);
const [name, setName] React.useState(BI);状态函数名要表意尽量聚集在一起申明方便查阅。
useRef
const dom React.useRef(null);useRef 尽量少用大量 Mutable 的数据会影响代码的可维护性。
但对于不需重复初始化的对象推荐使用 useRef 存储比如 new G2() 。
useReducer
局部状态不推荐使用 useReducer 会导致函数内部状态过于复杂难以阅读。 useReducer 建议在多组件间通信时结合 useContext 一起使用。
FAQ 可以在函数内直接申明普通常量或普通函数吗 不可以Function Component 每次渲染都会重新执行常量推荐放到函数外层避免性能问题函数推荐使用 useCallback 申明。
函数
所有 Function Component 内函数必须用 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 中进行比如刷新列表页获取基础信息或者进行搜索 都可以抽象为依赖了某些变量当这些变量变化时要重新取数
const { loading, error, value } useAsync(async () {return requestService.freshList(id);
}, [requestService.freshList, id]);组件间通信
简单的组件间通信使用透传 Props 变量的方式而频繁组件间通信使用 React.useContext 。
以一个复杂大组件为例如果组件内部拆分了很多模块 但需要共享很多内部状态 最佳实践如下
定义组件内共享状态 - store.ts
export const StoreContext React.createContext{state: State;dispatch: React.DispatchAction;
}(null)export interface State {};export interface Action { type: xxx } | { type: yyy };export const initState: State {};export const reducer: React.ReducerState, Action (state, action) {switch (action.type) {default:return state;}
};根组件注入共享状态 - main.ts
import { StoreContext, reducer, initState } from ./storeconst AppProvider: React.FC props {const [state, dispatch] React.useReducer(reducer, initState);return React.useMemo(() (StoreContext.Provider value{{ state, dispatch }}App //StoreContext.Provider), [state, dispatch])
};任意子组件访问/修改共享状态 - child.ts
import { StoreContext } from ./storeconst app: React.FC () {const { state, dispatch } React.useContext(StoreContext);return React.useMemo(() (div{state.name}/div), [state.name])
};如上解决了 多个联系紧密组件模块间便捷共享状态的问题 但有时也会遇到需要共享根组件 Props 的问题这种不可修改的状态不适合一并塞到 StoreContext 里我们新建一个 PropsContext 注入根组件的 Props
const PropsContext React.createContextProps(null)const AppProvider: React.FCProps props {return React.useMemo(() (PropsContext.Provider value{props}App //PropsContext.Provider), [props])
};结合项目数据流
参考 react-redux hooks。
debounce 优化
比如当输入框频繁输入时为了保证页面流畅我们会选择在 onChange 时进行 debounce 。然而在 Function Component 领域中我们有更优雅的方式实现。 其实在 Input 组件 onChange 使用 debounce 有一个问题就是当 Input 组件 受控 时 debounce 的值不能及时回填导致甚至无法输入的问题。 我们站在 Function Component 思维模式下思考这个问题
React scheduling 通过智能调度系统优化渲染优先级我们其实不用担心频繁变更状态会导致性能问题。如果联动一个文本还觉得慢吗 onChange 本不慢大部分使用值的组件也不慢没有必要从 onChange 源头开始就 debounce 。找到渲染性能最慢的组件比如 iframe 组件对一些频繁导致其渲染的入参进行 useDebounce 。
下面是一个性能很差的组件引用了变化频繁的 text 这个 text 可能是 onChange 触发改变的我们利用 useDebounce 将其变更的频率慢下来即可
const App: React.FC ({ text }) {// 无论 text 变化多快textDebounce 最多 1 秒修改一次const textDebounce useDebounce(text, 1000)return useMemo(() {// 使用 textDebounce但渲染速度很慢的一堆代码}, [textDebounce])
};使用 textDebounce 替代 text 可以将渲染频率控制在我们指定的范围内。
useEffect 注意事项
事实上useEffect 是最为怪异的 Hook也是最难使用的 Hook。比如下面这段代码
useEffect(() {props.onChange(props.id)
}, [props.onChange, props.id])如果 id 变化则调用 onChange。但如果上层代码并没有对 onChange 进行合理的封装导致每次刷新引用都会变动则会产生严重后果。我们假设父级代码是这么写的
class App {render() {return Child id{this.state.id} onChange{id this.setState({ id })} /}
}这样会导致死循环。虽然看上去 App 只是将更新 id 的时机交给了子元素 Child但由于 onChange 函数在每次渲染时都会重新生成因此引用总是在变化就会出现一个无限死循环
新 onChange - useEffect 依赖更新 - props.onChange - 父级重渲染 - 新 onChange…
想要阻止这个循环的发生只要改为 onChange{this.handleChange} 即可useEffect 对外部依赖苛刻的要求只有在整体项目都注意保持正确的引用时才能优雅生效。
然而被调用处代码怎么写并不受我们控制这就导致了不规范的父元素可能导致 React Hooks 产生死循环。
因此在使用 useEffect 时要注意调试上下文注意父级传递的参数引用是否正确如果引用传递不正确有两种做法
使用 useDeepCompareEffect 对依赖进行深比较。使用 useCurrentValue 对引用总是变化的 props 进行包装
function useCurrentValueT(value: T): React.RefObjectT {const ref React.useRef(null);ref.current value;return ref;
}const App: React.FC ({ onChange }) {const onChangeCurrent useCurrentValue(onChange)
};onChangeCurrent 的引用保持不变但每次都会指向最新的 props.onChange从而可以规避这个问题。
总结
如果还有补充欢迎在文末讨论。
如需了解 Function Component 或 Hooks 基础用法可以参考往期精读
精读《React Hooks》精读《怎么用 React Hooks 造轮子》精读《useEffect 完全指南》精读《Function Component 入门》 讨论地址是精读《React Hooks 最佳实践》 · Issue #202 · dt-fe/weekly