前言
目前来说,每个前端团队都有自己对 React Hooks 的使用偏好,在基于 hooks 的数据流管理或许没有所谓的最佳实践,但是开发过程中还是有一些准则可以参考。当你不熟悉一个新东西的时候,你就该去看它的文档。
React Hooks 是完全不同以往的心智模型,从使用开始我们就不应该以之前的思维去强行套用。要理解 Function Component 的最大特点就是更加彻底的状态驱动,虽然有很多文章写了如何用 Hooks 模拟生命周期,但是其实这里生命周期的概念早已名存实亡。
在实际使用中,我们只要牢牢记住“状态/流”的思想,所有的事件、请求和数据变化都依赖于该作用的“输入流”。把握住这点,我们再去考虑数据流变化后的触发效果,考虑作用是否需要触发?“输入流”是否可变?想清楚后就放心大胆地写吧。
流的思想
怎么通过流的思想去理解 Hooks 的使用呢?这里我们可以举几个例子:
逝者如斯夫,不舍昼夜
props 和 state 都是不可变的常量(const
)!每次 render 的时候,Function Component 相当于重新执行了一遍。每次都是新的 state,新的 effect,历史的状态则被保留在 React 的 “快照”中 。这里不再是this.state
的tihs
能够保留不变性,多次修改;而是可以理解成多次渲染生成多份状态。
正如官方文档的这个例子,不指定依赖的情况下,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
水之形,避高而趋下
正如水流只能从上游流向下游,在 React 中,状态也是单向的。因此,对于跨组件数据共享,由于数据只能从组件树的父节点传递给子节点,需要我们提供特殊的策略,比如 Context。
const ThemeContext = React.createContext(themes.default);
// 生产者
function App() {
const [color, setColor] = useState(themes.light);
return (
<ThemeContext.Provider value={color}>
<Toolbar1 />
<Toolbar2 />
</ThemeContext.Provider>
);
}
// 消费者
function Toolbar(props) {
const theme = useContext(ThemeContext);
return (
<div>
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
</div>
);
}
水是有流向性的,hooks 的也依赖于调用的顺序,我们可以把一个 state 作为参数传递给下一个 hooks。
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
//...
)
}
流的实践
其实用 hooks 在一些应用场景中,可以实现很简洁的写法。比如请求一个 List,我们把“分页”、“查询”、“重刷”都当作状态来处理,请求操作只要依赖这些状态即可。这样看来,这里的用法其实很像 Rxjs 的combineLatest
。
const App = () => {
const [pagination, setPagination] = useState<object>(defaultPagination);
const [searchValue, setSearchValue] = useState<object>(defaultSearchValue);
const [refresh, setRefresh] = useState<number>(0);
useEffect(() => {
// fetch
}, [pagination, searchValue, refresh])
};
Hooks 优化实践
个人实践总结,hooks 的优化一般可以从两个方面入手:
- 一是通过比较,需要的时候才进行渲染
- 二是使用不依赖/不变量跳过渲染
compare 优化
由于父组件每次 render 都可能引起 props 的改变,因此传递的值尽量使用 useMemo
或者 useCallback
优化。
useCompareEffect
// https://github.com/kentcdodds/use-deep-compare-effect
type Effect = typeof React.useEffect;
type EffectDeps = Parameters<Effect>[1];
export const useDeepCompareEffect: Effect = (effect, deps?) => {
const prevRef = useRef<EffectDeps>();
const deepDep = useRef<number>(0);
if (!deepEqual(prevRef.current, deps)) {
prevRef.current = deps;
deepDep.current = deepDep.current + 1;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return useEffect(effect, [deepDep.current]);
};
useDeepCompareMemo
type Memo = typeof React.useMemo;
type MemoDeps = Parameters<Memo>[1];
export const useDeepCompareMemo: Memo = (factory, deps?) => {
const prevRef = useRef<MemoDeps>();
const deepDep = useRef<number>(0);
if (!deepEqual(prevRef.current, deps)) {
prevRef.current = deps;
deepDep.current = deepDep.current + 1;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(factory, [deepDep.current]);
};
usePrevious
如果我们想拿到上次渲染的状态,再进行比较,可以用usePrevious
// https://github.com/streamich/react-use
export default function usePrevious<T>(state: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = state;
});
return ref.current;
}
// compare
const App = () => {
const [count, setCount] = useState<number>(0);
const prevCount = usePrevious<number>(count);
useEffect(() => {
if(count > prevCount + 5) {
//...
}
}, [count])
}
不依赖/不变量
- defaultProps
对于不变的默认参数,我们可以写在函数以外
const defaultValue = { status: "OPEN" };
const App = ({ value = defaultValue }) => {
//...
};
// or
App.defaultProps = {
value: { status: "OPEN" }
}
useRef
对于某个数据,如果不需要被依赖又要保证最新的值可以保存在ref
const App = ({ onChange }) => {
const callback = useRef(onChange);
callback.current = onChange;
useEffect(() => {
//...
dom.addEventListener("click", callback.current);
//...
}, [])
};
- 函数式 setState
const App = () => {
const [count, setCount] = useState<number>(0);
useEffect(() => {
setCount(count => count + 1);
}, [])
};
总结
主要还是讲了个人的一些实践经验,前端数据流这部分还有很多内容需要理解和学习,先做下记录吧~
参考文章
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!