React 16.7.0-alpha 版本新特性, 主要为了解决状态共享的问题, 继
render-props
和higher-order components
之后的第三种状态共享方案,不会产生 JSX 嵌套地狱,尽管现在已经是 2021 年 10 月了,不过以以前的角度来回顾一下出了已久的特性也能方便加深一下印象。同时,反向去理解 hooks 本身这个含义,怎么在自己代码里面书写颗粒度更细的代码。
- 状态共享 -> 状态逻辑复用, 只共享数据处理逻辑,不会共享数据本身
- 两段代码来看看不同点:
// render props
function App () {
return (
<Toogle initial={false}>
{({on, toggle}) => (
<Button type="primary" onClick={toggle}>Open Modal</Button>
<Modal visible={on} onOk={toggle} onCancel={toggle}></Modal>
)}
</Toogle>
)
}
function App() {
const [state, setState] = useState(false)
return (
<>
<Button type="primary" onClick={() => {setState(true)}}></Button>
<Modal visible={state} onOk={() => setState(false)} onCancel={() => setState(false)}></Modal>
</>
)
}
- React hooks 像一个内置打平的 renderProps 库,可以随时创建一个值和修改这个值的方法,看上去像 function 形式的 setState ,并等价于依赖注入,与使用 setState 相比,组件没有状态
-
React Hooks 带来的好处是更 FP, 更新粒度更细,代码更清晰,特性还有如下三点:
-
多个状态不会产生嵌套,写法还是平铺(renderProps 可以通过 compose 解决,使用略为繁琐,而且因为强制封装一个新对象而增加了实体数量) (有状态的组件没有渲染,有渲染的组件没有状态)
import { useState, useEffect } from 'react' // 底层hooks, 返回boolean function useFriendStatusBoolean(friendId) { function handleStatusChange(status) { setIsOnline(status.isOnline) } useEffect(() =>{ ChatAPI.subscribeToFriendStatus(firendId, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); } }) return isOnline; } // 上层hooks, 根据在线状态返回字符串 function useFriendStatusString(props) { const isOnline = useFriendStatusBoolean(props.friend.id); if (isOnline === null) { return "loading ..."; } return isOnline ? "Online" : "offline"; } // 使用底层hooks的UI function FriendListItem(props) { const isOnline = useFriendStatusBoolean(props.friend.id); return ( <li style={{ color: isonLine ? "green" : "black" }}>{props.friend.name}</li> ); } // 使用上层Hooks 的 UI function FriendListStatus(props) { const status = useFriendStatusString(props); return <li>{status}</li> } // 对于useFriendStatusBoolean 和 useFriendStatusString ,后者的hooks利用前者的hooks生成新的hooks,两个hooks给不同的UI使用,因为两个hooks数据是联动的,所以UI状态也是联动的。 // 有状态组件(有useState, 没有渲染(返回非UI的值)),可以被任何UI组件调用 // 有渲染的组件(返回JSX), 没有状态(没有使用useState),就是一个纯函数UI组件
-
利用 useState 创建 Redux
function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action) setState(nextState); } return [state, dispatch]; }
-
利用 useEffect 代替一些生命周期(细碎的代码片段整合成一个完整的代码块)
// 在useState位置附近,使用useEffect处理副作用 useEffect(() => { const subscription = props.source.subcribe(); return () => { // clean up the subscription subscription } })
-
-
Hooks 可以引用其他 Hooks
-
组件 UI 与状态分离
- Hooks 必须以 use 命名开头,方便做 eslint 检查,放置用 condition 判断包裹 useHook
- React hooks 不通过 Proxy 或者 getters 实现,而是通过链表实现,每次
useState
都会改变下标,useState 被包裹在 condition 中的话就会导致执行下标对不上,导致 useState 导出的 setter 更新错数据 (lane 执行队列更新机制)
-
如果一个 Hook 不产生 UI,那么他可以永远被其他 hook 封装,虽然允许有副作用,但是被包裹在 useEffect 里,总体还是函数式的,hooks 如果要集中在 UI 函数顶部写,很容易养成书写无状态 UI 组件的习惯,践行状态与 UI 分离的理念。
-
状态究竟是什么
function App() { const [state, setState] = useState() return (<span>{state}</span>) } // useState 算是无状态,因为React hooks本质就是renderProps 或者HOC的一个写法,换成renderProps <Count>{(state, setState) => <App state={state} setState={setState}></App>}</Count> function App(props) { return <span>{props.state}</span> } // App组件无状态,输入输出完全由输入(prpos)决定 // 有状态无UI组件就是useState function useState() { const [state, setState] = useState(0) // 函数内部设置值(状态)内部决定状态 reutrn [state, setState] }
-
为了最佳实践,避免 App 自己维护状态,父级的 RenderProps 组件可以维护状态,可以考虑在有状态的组件没有渲染,有渲染的组件没有状态,没渲染的组件也可以没状态
- 把 React hooks 当做更便捷的 renderProps 使用,虽然写法上看起来是内部维护了一个状态,单其实等价于注入,Connect, HOC 或者 renderProps, 如此 renderProps 门槛降低不少,同时也可以大量抽象 Custom Hooks ,让代码更加 FP, 不会嵌套更多层级。