一、什么是hooks?
成都创新互联主营景宁畲族自治网站建设的网络公司,主营网站建设方案,成都APP应用开发,景宁畲族自治h5小程序开发搭建,景宁畲族自治网站营销推广欢迎景宁畲族自治等地区企业咨询
react 于19年2月份在16.8版本中新增了hook这一特性,已经过去了半年多了,社区的各种文章解析页汗牛充栋,本文将结合自己的项目实践,对react hooks做一个全面的讲解,俗话说没吃过猪肉,还没见过猪跑吗?确实,可能大部分同学对hooks特性都有了自己的了解,但是在实际项目中使用又是另一回事了,实践出真知,这篇文章是自己对react hooks的理解,也是在上一个项目中使用react hooks的总结
看着猪跑一千次,不如自己吃一次猪肉。
二、为什么需要hooks?
- export default translate('[index,tips]')(withStyles(styles, { withTheme: true })(connect(mapStateToProps,mapDispatchToProps)(Alert)));
- 其实如果我们还可以将 `withTheme`也提取成一个高阶函数,那么我们的组件就将由现在的3层变成4层,实际使用的时候可能还有别的属性通过别的高阶函数添加,嵌套层级就会更深。给人明显的感觉就是不够直观。
- 如果你答不上来,可以戳一下下面两个链接。
三、useState hook 的执行过程追踪
useState,
useEffect,
useContext,
和几个额外的 Hook:
useReducer,
useCallback,
useMemo,
useRef,
useImperativeHandle,
useLayoutEffect,
useDebugValue ,
他们的作用各不相同,但是可以这么总结一下:让Function Component有状态(state),流氓不可怕,就怕流氓有文化。当我们给比较有优势的FC 插上state的翅膀之后,他就要起飞了。原来ClassComponent能干的事情他也能干起来了,加上前文分析的优势,还干的更加有声有色。这里我们使用useState做一个全面的解析,
首先我们来看一下一个简单的的计数器,点击click 按钮,state加1并渲染到页面上:
ClassComponent实现:
- import React from 'react';
- interface ITestState {
- count: number;
- }
- class Test extends React.Component<{}, ITestState> {
- constructor(props: {}) {
- super(props);
- this.state = {
- count: 0
- };
- }
- public handleClick = () => {
- const { count } = this.state;
- this.setState({ count: count + 1 });
- }
- public render() {
- return (
- <>
{this.state.count}- >
- );
- }
- }
- export default Test;
hooks实现:
- import React, { useState } from 'react';
- const Test: React.FunctionComponent<{}> = () => {
- const [count, setCount] = useState
(0); - return (
- <>
{count}- >
- );
- };
- export default Test;
- public handleClick = () => {
- const { count } = this.state;
- this.setState({ count: count + 1 });
- }
- export function useState
(initialState: (() => S) | S) {- const dispatcher = resolveDispatcher();
- return dispatcher.useState(initialState);
- }
这个函数接收一个参数initialState: (() => S) | S,初始state的函数或者我们的state初始值。
然后调用
- dispatcher.useState(initialState);,这里我们看一下dispatcher是怎么来的:
- function resolveDispatcher() {
- const dispatcher = ReactCurrentDispatcher.current;
- ...
- return dispatcher;
- }
发现是通过ReactCurrentDispatcher.current得到,那ReactCurrentDispatcher又是何方神圣呢?
我们进一步看看它怎么来的
- import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';
- const ReactCurrentDispatcher = {
- current: (null: null | Dispatcher),
- };
- export default ReactCurrentDispatcher;
根据type,我们可以判断dispatcher的类型是react-reconciler/src/ReactFiberHooks里面定义的Dispatcher,可以看到这个current属性是个null。那它是什么时候被赋值的呢?
我们来看看functionComponent的render过程renderWithHooks,
- export function renderWithHooks(
- current: Fiber | null,
- workInProgress: Fiber,
- Component: any,
- props: any,
- refOrContext: any,
- nextRenderExpirationTime: ExpirationTime,
- ): any{
- ....
- if (__DEV__) {
- ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
- } else {
- ReactCurrentDispatcher.current =
- nextCurrentHook === null
- ? HooksDispatcherOnMount
- : HooksDispatcherOnUpdate;
- }
- }
这里react源码根据nextCurrentHook做了一些判断,我移除掉了,只关注ReactCurrentDispatcher.current的值,可以看到它的取值分为两种,HooksDispatcherOnMount和 HooksDispatcherOnUpdate分别对应mount/update两个组件状态;这里我们先看HooksDispatcherOnMount:
- const HooksDispatcherOnMount: Dispatcher = {
- ...
- useState: mountState,
- ...
- };
这就是我们寻寻觅觅的Dispatcher的长相,最终我们useState在组件mount的时候执行的就是这个mountState了,那我们就迫不及待如饥似渴的来看看mountState又做了什么吧。
- function mountState
(- initialState: (() => S) | S,
- ): [S, Dispatch
>] { - const hook = mountWorkInProgressHook();
- if (typeof initialState === 'function') {
- initialStateinitialState = initialState();
- }
- hookhook.memoizedState = hook.baseState = initialState;
- const queue = (hook.queue = {
- last: null,
- dispatch: null,
- lastRenderedReducer: basicStateReducer,
- lastRenderedState: (initialState: any),
- });
- const dispatch: Dispatch<
- BasicStateAction
,- > = (queue.dispatch = (dispatchAction.bind(
- null,
- // Flow doesn't know this is non-null, but we do.
- ((currentlyRenderingFiber: any): Fiber),
- queue,
- ): any));
- return [hook.memoizedState, dispatch];
- }
进入这个函数首先执行的mountWorkInProgressHook()获取到当前的workInProgressHook,看这个名字就知道他是和workInProgress分不开了,这个workInProgress代表了当前正在处理的fiber,fiber是当前组件的需要完成或者已经完成的work的对象,也可以理解为我们的这个正在执行mountState的组件的各种数据和状态的集合。我们来具体的看一下mountWorkInProgressHook的执行逻辑:
- function mountWorkInProgressHook(): Hook {
- const hook: Hook = {
- memoizedState: null,
- baseState: null,
- queue: null,
- baseUpdate: null,
- next: null,
- };
- if (workInProgressHook === null) {
- // This is the first hook in the list
- firstWorkInProgressHook = workInProgressHook = hook;
- } else {
- // Append to the end of the list
- workInProgressHookworkInProgressHook = workInProgressHook.next = hook;
- }
- return workInProgressHook;
- }
判断当前fiber的workInProgressHook是不是null,如果是,将全新的hook赋值给全局的workInProgressHook和firstWorkInProgressHook,否则,将初始值赋值给workInProgressHook。相当于mountState里面的hook值就是
- const hook: Hook = {
- memoizedState: null,
- baseState: null,
- queue: null,
- baseUpdate: null,
- next: null,
- };
实际上,workInProgressHook是这样的一个链表结构,React里面广泛使用了这样的结构存储副作用。
- {
- memoizedState: null,
- baseState: null,
- queue: null,
- baseUpdate: null,
- next: {
- ...
- next: {
- ...
- next: {
- next: {...},
- ...
- },
- },
- }
- }
继续往下看:
- if (typeof initialState === 'function') {
- initialStateinitialState = initialState();
- }
- hookhook.memoizedState = hook.baseState = initialState;
useState接收的参数类型如果是函数,这里就会执行传进来的函数获取initialState,赋值给hook.memoizedState = hook.baseState这两个属性,再往下,建立了当前hook的更新队列queue:
return [hook.memoizedState, dispatch];
将state和setState以数组的形式返回,这也是我们使用useState hook的正确姿势。到这里相信大家都很清楚了,useState通过将我们的初始state暂存到workInProgressHook的memoizedState中,每次更新的时候通过dispatchAction更新workInProgressHook。
我们回过头来再看看刚才没深入过的queue,通过类型我们可以知道他是
- type UpdateQueue
= {- last: Update
| null,- dispatch: (A => mixed) | null,
- lastRenderedReducer: ((S, A) => S) | null,
- lastRenderedState: S | null,
- };
看到这个结构,熟悉react fiber的同学已经心中有数了,它的last属性是一个链表,用来存储当前hook的变化信息,能够通过next迭代处理所有变更信息和状态。这里我们就到此为止,感兴趣的同志可以自行深入琢磨,对于这个hook,掌握到这里已经够了,很多文章说useState和useReducer的基友关系,从这里我们就看出来了,useState最终使用的也是useReducer一致的api,通过类似redux的理念,通过dispatchAction修改state,有兴趣的同志可以看这里useReducer源码;
四、自定义hooks
阿西吧,东拉西扯的到了这块最有趣的地方。这块以项目中实际用到的几个hook来举例说明。先说一下,其实官方的hook已经很多很全了,状态我们可以useState,复杂多状态我们可以用useReducer,共享和传递状态可以使用useContext,引用组件、引用状态可以useRef,组件render完成之后的操作通过useEffect完成...还有其他几个hook,那么我们为什么还需要自定义hooks呢?
useWindowLoad
- const executeOnload:()=>{alert('alert after loaded')}
传统的实现思路:
- {
- if(window.loaded)executeOnload();return;
- const old = window.onload;
- window.onload = () => {
- window.loaded = true;
- executeOnload();
- old && old();
- };
- }
在使用我们的自定义hook useWindowLoad之后
- const isWindowLoaded= useWindowLoad(executeOnload)
每一处需要监听的地方都变得十分简单有没有,话不多说,直接上码:
- export default function useWindowLoad(func?: (params?: any) => any): boolean {
- useEffect(() => {
- let effect: (() => void) | nullnull = null;
- const old = window.onload;
- window.onload = () => {
- effect = func && func();
- old && old();
- window.loaded = true;
- };
- return () => {
- if (typeof effect === 'function') {
- effect();
- }
- };
- });
- return window.loaded;
- })
最后,我们返回load状态。这里我们主要使用了useEffect这个hook,并在接受的参数的返回值中清除了对应的副作用。useEffect在每次组件render完成后执行,具体使用参考文档。注意,副作用的清除很重要,因为我们不能保证传入的回调函数不会带来副作用,所以使用时应该传递return一个函数的函数作为参数
useMessage
- 这样一个场景:我们需要一个全局的消息提示,已经写好了一个全局组件,并通过redux管理状态控制Message的显示和隐藏,这其实是一个很常见的功能,在使用hook之前,我们的实现可能是这样的:
- import React from 'react';
- import { connect } from 'react-redux';
- import { message } from './actions';
- import Errors from './lib/errors';
- interface IDemoProps {
- message(params: Message): void;
- }
- const mapStateToProps = (state: IStore) => ({});
- const mapDispatchToProps = (dispatch: any) => ({
- message: (params: Message) =>dispatch(message(params))
- });
- class Demo extends React.Component
{ - public handleClick() {
- this.props.message({ content: Errors.GLOBAL_NETWORK_ERROR.message, type: 'error', duration: 1600, show: true });
- }
- public render() {
- return ;
- }
- }
- export default connect(mapStateToProps, mapDispatchToProps)(Demo);
每次我们要使用就得mapDispatchToProps,引入action,connect,...繁琐至极,我们也可以用**高阶组件**包装一下,透传一个message函数给需要的子组件,这里我们使用自定义hook来解决,先看看最终达到的效果:
- import React from 'react';
- import Errors from './lib/errors';
- const Demo: React.FC<{}> = () => {
- const message = useMessage();
- const handleClick = () => {
- message.info(content: Errors.GLOBAL_NETWORK_ERROR.message);
- };
- return ;
- };
- export default Demo;
- 简单了许多,每次需要全局提示的地方,我们只需要通过`const message = useMessage();`
- 然后再组件内部任何地方使用`message.info('content')`,`message.error('content')`,`message.success('content')`,`message.warn('content')`即可,再也不关心action,redux connect等一系列操作。
- 我们来看看这个逻辑如何实现的:
- import { useDispatch } from 'react-redux';
- import { message as alert } from '../actions/index';
- /**
- * @param {type}
- * @return:
- */
- export default function useMessage() {
- const dispatch = useDispatch();
- const info = (content: Partial
['content']) => dispatch(alert({ content, duration: 3000, show: true, type: 'info' })); - const warn = (content: Partial
['content']) => dispatch(alert({ content, duration: 3000, show: true, type: 'warn' })); - const error = (content: Partial
['content']) => dispatch(alert({ content, duration: 3000, show: true, type: 'error' })); - const success = (content: Partial
['content']) => dispatch(alert({ content, duration: 3000, show: true, type: 'success' })); - const message = {
- success,
- info,
- warn,
- error
- };
- return message;
- }
我们内部使用useDispatch拿到dispatch,封装了四个不同功能的函数,直接对外提供封装好的对象,就实现使用上了类似antd message组件的功能,哪里需要哪里useMessage就可以开心的玩耍了。
- 项目中还有其他的自定义hook,但是思路很上面两个一致,提取共性,消除副作用。 这里给大家推荐一个自定义的hook的一个[站点](https://usehooks.com)。我从这里吸收了一些经验。
五、总结
分享标题:Reacthooks实战总结
当前路径:http://www.shufengxianlan.com/qtweb/news39/554639.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联