You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importReact,{useState}from'react';functionExample(){// Declare a new state variable, which we'll call "count"const[count,setCount]=useState(0);return(<div><p>You clicked {count} times</p><buttononClick={()=>setCount(count+1)}>
Click me
</button></div>);}
现代工具无法很好地压缩 class 代码,导致代码体积偏大,hot reloading效果也不太稳定。
为了解决上述问题,hooks 应运而生。让我们使用 hooks 改造下上面的例子
importReact,{useState,useEffect}from'react';functionuseFriendStatus(friendID){const[isOnline,setIsOnline]=useState(null);useEffect(()=>{functionhandleStatusChange(status){setIsOnline(status.isOnline);}ChatAPI.subscribeToFriendStatus(props.friend.id,handleStatusChange);// Specify how to clean up after this effect:returnfunctioncleanup(){ChatAPI.unsubscribeFromFriendStatus(props.friend.id,handleStatusChange);};});returnisOnline;}functionFriendStatusWithCounter(props){const[count,setCount]=useState(0);useEffect(()=>{document.title=`You clicked ${count} times`;});constisOnline=useFriendStatus(props.friend.id);return(<div><p>You clicked {count} times</p><p>Friend {props.friend.id} status: {isOnline}</p><buttononClick={()=>setCount(count+1)}>
Click me
</button></div>);}functionFriendStatus(props){// 通过自定义hook复用逻辑constisOnline=useFriendStatus(props.friend.id);if(isOnline===null){return'Loading...';}returnisOnline ? 'Online' : 'Offline';}
useEffect(()=>{functionhandleStatusChange(status){setIsOnline(status.isOnline);}ChatAPI.subscribeToFriendStatus(props.friend.id,handleStatusChange);// Specify how to clean up after this effect:returnfunctioncleanup(){ChatAPI.unsubscribeFromFriendStatus(props.friend.id,handleStatusChange);};});
需要注意,上面这种写法,每次组件更新都会执行 effect 的回调函数和清理函数,顺序如下:
// Mount with { friend: { id: 100 } } propsChatAPI.subscribeToFriendStatus(100,handleStatusChange);// Run first effect// Update with { friend: { id: 200 } } propsChatAPI.unsubscribeFromFriendStatus(100,handleStatusChange);// Clean up previous effectChatAPI.subscribeToFriendStatus(200,handleStatusChange);// Run next effect// Update with { friend: { id: 300 } } propsChatAPI.unsubscribeFromFriendStatus(200,handleStatusChange);// Clean up previous effectChatAPI.subscribeToFriendStatus(300,handleStatusChange);// Run next effect// UnmountChatAPI.unsubscribeFromFriendStatus(300,handleStatusChange);// Clean up last effect
这个效果等同于在componentDidMount、componentDidUpdate和componentWillUnmount实现了事件绑定和解绑。如果只是组件的 state 变化导致重新渲染,同样会重新调用 cleanup 和 effect,这时候就显得没有必要了,所以 useEffect 支持用第2个参数来声明依赖
functionExample(){const[count,setCount]=useState(0);useEffect(()=>{document.title=`You clicked ${count} times`;},[count]);const[name,setName]=useState('Joe');useEffect(()=>{console.log(`Your name is ${name}`);});return(<div><p>You clicked {count} times</p><buttononClick={()=>setCount(count+1)}>
Click me
</button></div>);}
根据官方的说法,在可见的未来 react team 并不会停止对 class component 的支持,因为现在绝大多数 react 组件都是以 class 形式存在的,要全部改造并不现实,而且 hooks 目前还不能完全取代 class,比如getSnapshotBeforeUpdate和componentDidCatch这2个生命周期,hooks还没有对等的实现办法。建议大家可以在新开发的组件中尝试使用 hooks。如果经过长时间的迭代后 function components + hooks 成为主流,且 hooks 从功能上可以完全替代 class,那么 react team 应该就可以考虑把 class component 移除,毕竟没有必要维护2套实现,这样不仅增加了维护成本,对开发者来说也多一份学习负担。
什么是hooks?
hooks 是 react 在16.8版本开始引入的一个新功能,它扩展了函数组件的功能,使得函数组件也能实现状态、生命周期等复杂逻辑。
上面是 react 官方提供的 hooks 示例,使用了内置hook
useState
,对应到Class Component应该这么实现简而言之,hooks 就是钩子,让你能更方便地使用react相关功能。
hooks解决了什么问题?
看完上面一段,你可能会觉得除了代码块精简了点,没看出什么好处。别急,继续往下看。
过去,我们习惯于使用Class Component,但是它存在几个问题:
状态逻辑复用困难
side effect 复用和组织困难
复用的问题就不说了,跟状态逻辑一样,主要说下代码组织的问题。1. 为了在组件刷新的时候更新文档的标题,我们在
componentDidMount
和componentDidUpdate
中各写了一遍更新逻辑; 2. 绑定朋友状态更新和解绑的逻辑,分散在componentDidMount
和componentWillUnmount
中,实际上这是一对有关联的逻辑,如果能写在一起最好;3.componentDidMount
中包含了更新文档标题和绑定事件监听,这2个操作本身没有关联,如果能分开到不同的代码块中更利于维护。Javascript Class 天生缺陷
this
的指向问题,需要记得手动 bind 事件处理函数,这样代码看起来很繁琐,除非引入@babel/plugin-proposal-class-properties
(这个提案目前还不稳定)。为了解决上述问题,hooks 应运而生。让我们使用 hooks 改造下上面的例子
看,问题都解决了!
怎么使用?
hooks 一般配合Function Components使用,也可以在内置 hooks 的基础上封装自定义 hook。
先介绍下 react 提供的内置 hooks。
useState
useState
接收一个参数作为初始值,返回一个数组,数组的第一个元素是表示当前状态值的变量,第二个参数是修改状态的函数,执行的操作类似于this.setState({ count: someValue })
,当然内部的实现并非如此,这里仅为了帮助理解。useState
可以多次调用,每次当你需要声明一个state时,就调用一次。需要更新某个具体状态时,调用对应的 setXXX 函数即可。
useEffect
useEffect的作用是让你在Function Components里面可以执行一些 side effects,比如设置监听、操作dom、定时器、请求等。
需要注意,上面这种写法,每次组件更新都会执行 effect 的回调函数和清理函数,顺序如下:
这个效果等同于在
componentDidMount
、componentDidUpdate
和componentWillUnmount
实现了事件绑定和解绑。如果只是组件的 state 变化导致重新渲染,同样会重新调用 cleanup 和 effect,这时候就显得没有必要了,所以 useEffect 支持用第2个参数来声明依赖第2个参数是一个数组,在数组中传入依赖的 state 或者 props,如果依赖没有更新,就不会重新执行 cleanup 和 effect。
如果你需要的是只在初次渲染的时候执行一次 effect,组件卸载的时候执行一次 cleanup,那么可以传一个空数组
[]
作为依赖。useContext
context
这个概念大家应该不陌生,一般用于比较简单的共享数据的场景。useContext
就是用于实现context
功能的 hook。来看下官方提供的示例
代码挺长,但是一眼就能看懂了。把 context 对象传入
useContext
,就可以拿到最新的 context value。需要注意的是,只要使用了
useContext
的组件,在 context value 改变后,一定会触发组件的更新,哪怕他使用了React.memo
或是shouldComponentUpdate
。useReducer
useReducer(reducer, initialArg)
返回[state, dispatch]
,跟 redux 很像。除此之外,react 内置的 hooks 还包括
useCallback
、useMemo
、useRef
、useImperativeHandle
、useLayoutEffect
和useDebugValue
,这里就不再赘述了,可以直接参考官方文档。自定义 hook
基于内置 hook,我们可以封装自定义的 hook,上面的示例中已经出现过
useFriendStatus
这样的自定义 hook,它能帮我们抽离公共的组件逻辑,方便复用。注意,自定义 hook 也需要以use
开头。我们可以根据需要创建各种场景的自定义 hook,如表单处理、计时器等。后面实战场景的章节中我会具体介绍几个例子。
实现原理
hooks 的使用需要遵循几个规则:
之所以有这些规则限制,是跟 hooks 的实现原理有关。
这里我们尝试实现一个简单的版本的
useState
和useEffect
用来说明。此时我们需要构造一个函数组件来使用这2个 hooks
[]
,cursor 为0
const [count, setCount] = useState(0);
,memoHooks 为[0]
,cursor 为0
useEffect(() => { document.title =
You clicked ${count} times; }, [count]);
,memoHooks 为[0, [0]]
,cursor 为1
const [name, setName] = useState('Joe');
,memoHooks 为[0, [0], 'Joe']
,cursor 为2
useEffect(() => { console.log(
Your name is ${name}); });
,memoHooks 为[0, [0], 'Joe', undefined]
,cursor 为3
setCount(count + 1)
,memoHooks 为[1, [0], 'Joe', undefined]
,cursor 为0
const [count, setCount] = useState(0);
,memoHooks 为[1, [0], 'Joe', undefined]
,cursor 为0
useEffect(() => { document.title =
You clicked ${count} times; }, [count]);
,memoHooks 为[1, [1], 'Joe', undefined]
,cursor 为1
。这里由于hooks[1]
的值变化,会导致 cb 再次执行。const [name, setName] = useState('Joe');
,memoHooks 为[1, [1], 'Joe', undefined]
,cursor 为2
useEffect(() => { console.log(
Your name is ${name}); });
,memoHooks 为[1, [1], 'Joe', undefined]
,cursor 为3
。这里由于依赖为 undefined,导致 cb 再次执行。通过上述示例,应该可以解答为什么 hooks 要有这样的使用规则了。
memoHooks
和cursor
其实都跟当前渲染的组件实例绑定,脱离了Function Components,hooks 也无法正确执行。当然,这些只是为了方便理解做的一个简单demo,react 内部实际上是通过一个单向链表来实现,并非 array,有兴趣可以自行翻阅源码。
实战场景
操作表单
实现一个hook,支持自动获取输入框的内容。
网络请求
实现一个网络请求hook,能够支持初次渲染后自动发请求,也可以手动请求。参数传入一个请求函数即可。
上面2个例子只是实战场景中很小的一部分,却足以看出 hooks 的强大,当我们有丰富的封装好的 hooks 时,业务逻辑代码会变得很简洁。推荐一个github repo,这里罗列了很多社区产出的 hooks lib,有需要自取。
使用建议
根据官方的说法,在可见的未来 react team 并不会停止对 class component 的支持,因为现在绝大多数 react 组件都是以 class 形式存在的,要全部改造并不现实,而且 hooks 目前还不能完全取代 class,比如
getSnapshotBeforeUpdate
和componentDidCatch
这2个生命周期,hooks还没有对等的实现办法。建议大家可以在新开发的组件中尝试使用 hooks。如果经过长时间的迭代后 function components + hooks 成为主流,且 hooks 从功能上可以完全替代 class,那么 react team 应该就可以考虑把 class component 移除,毕竟没有必要维护2套实现,这样不仅增加了维护成本,对开发者来说也多一份学习负担。参考文章
The text was updated successfully, but these errors were encountered: