百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 博客教程 > 正文

React系列-自定义Hooks很简单

connygpt 2024-08-20 14:18 10 浏览

React系列-Mixin、HOC、Render Props(上)

React系列-轻松学会Hooks(中)

React系列-自定义Hooks很简单(下)

我们在第二篇文章中介绍了一些常用的hooks,接着我们继续来介绍剩下的hooks吧

useReducer

作为useState 的替代方案。它接收一个形如(state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

不明白Redux工作流的同学可以看看这篇Redux系列之分析中间件原理(附经验分享)

为什么使用

官方说法: 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

总结来说:

  • 如果你的state是一个数组或者对象等复杂数据结构

  • 如果你的state变化很复杂,经常一个操作需要修改很多state

  • 如果你希望构建自动化测试用例来保证程序的稳定性

  • 如果你需要在深层子组件里面去修改一些状态(也就是useReducer+useContext代替Redux)

  • 如果你用应用程序比较大,希望UI和业务能够分开维护

登录场景

举个例子??:

登录场景

useState完成登录场景

Bash
????function?LoginPage()?{????????const?[name,?setName]?=?useState('');?//?用户名????????const?[pwd,?setPwd]?=?useState('');?//?密码????????const?[isLoading,?setIsLoading]?=?useState(false);?//?是否展示loading,发送请求中????????const?[error,?setError]?=?useState('');?//?错误信息????????const?[isLoggedIn,?setIsLoggedIn]?=?useState(false);?//?是否登录????????const?login?=?(event)?=>?{????????????event.preventDefault();????????????setError('');????????????setIsLoading(true);????????????login({?name,?pwd?})????????????????.then(()?=>?{????????????????????setIsLoggedIn(true);????????????????????setIsLoading(false);????????????????})????????????????.catch((error)?=>?{????????????????????//?登录失败:?显示错误信息、清空输入框用户名、密码、清除loading标识????????????????????setError(error.message);????????????????????setName('');????????????????????setPwd('');????????????????????setIsLoading(false);????????????????});????????}????????return?(?????????????//??返回页面JSX?Element????????)????}

useReducer完成登录场景

Bash
????const?initState?=?{????????name:?'',????????pwd:?'',????????isLoading:?false,????????error:?'',????????isLoggedIn:?false,????}????function?loginReducer(state,?action)?{????????switch(action.type)?{????????????case?'login':????????????????return?{????????????????????...state,????????????????????isLoading:?true,????????????????????error:?'',????????????????}????????????case?'success':????????????????return?{????????????????????...state,????????????????????isLoggedIn:?true,????????????????????isLoading:?false,????????????????}????????????case?'error':????????????????return?{????????????????????...state,????????????????????error:?action.payload.error,????????????????????name:?'',????????????????????pwd:?'',????????????????????isLoading:?false,????????????????}????????????default:?????????????????return?state;????????}????}????function?LoginPage()?{????????const?[state,?dispatch]?=?useReducer(loginReducer,?initState);????????const?{?name,?pwd,?isLoading,?error,?isLoggedIn?}?=?state;????????const?login?=?(event)?=>?{????????????event.preventDefault();????????????dispatch({?type:?'login'?});????????????login({?name,?pwd?})????????????????.then(()?=>?{????????????????????dispatch({?type:?'success'?});????????????????})????????????????.catch((error)?=>?{????????????????????dispatch({????????????????????????type:?'error'????????????????????????payload:?{?error:?error.message?}????????????????????});????????????????});????????}????????return?(?????????????//??返回页面JSX?Element????????)????}

??我们的state变化很复杂,经常一个操作需要修改很多state,另一个好处是所有的state处理都集中到了一起,使得我们对state的变化更有掌控力,同时也更容易复用state逻辑变化代码,比如在其他函数中也需要触发登录success状态,只需要dispatch({ type: 'success' })。

笔者[狗头]认为,暂时应该不会用useReducer替代useState,毕竟Redux的写法实在是很繁琐

复杂数据结构场景

刚好最近笔者的项目就碰到了复杂数据结构场景,可是并没有用useReducer来解决,依旧采用useState,原因很简单:方便

//?定义list类型
??export?interface?IDictList?extends?IList?{
??extChild:?{
????curPage:?number
????totalSize:?number
????size:?number?//?pageSize
????list:?IList[]
???}
?}
?const?[list,?setList]?=?useState<IDictList[]>([])
?
?const?change=()=>{
???const?datalist?=?JSON.parse(JSON.stringify(list))?//?拷贝对象?地址不同?不过这种写法感觉不好?建议用reducers?应该封装下reducers写法
???const?data?=?await?getData()
??????const?{?totalCount,?pageSize,?list?}?=?data
??????item.extChild.totalSize?=?totalCount
??????item.extChild.size?=?pageSize
??????item.extChild.list?=?list
??????setList(datalist)?//?改变
?}

看typescript写的类型声明就知道了这个list变量是个复杂的数据结构,需要经常需要改变添加extChild.list数组的内容,但是这种Array.prototype.push,是不会触发更新,做过是通过const datalist = JSON.parse(JSON.stringify(list))。虽然没有使用useReducer进行替代,笔者还是推荐大家试试

如何使用

const?[state,?dispatch]?=?useReducer(reducer,?initialArg,?init);

知识点合集

引用不变

useReducer返回的state跟ref一样,引用是不变的,不会随着函数组件的重新更新而变化,因此useReducer也可以解决闭包陷阱

const?setCountReducer?=?(state,action)=>{
??switch(action.type){
????case?'add':
??????return?state+action.value
????case?'minus':
??????return?state-action.value
????default:
??????return?state
??}
}

const?App?=?()=>{
??const?[count,dispatch]?=?useReducer(setCountReducer,0)
??useEffect(()=>{
????const?timeId?=?setInterval(()=>{
??????dispatch({type:'add',value:1})
????},1000)
????return?()=>?clearInterval(timeId)
??},[])
??return?(
????<span>{count}</span>
??)
}

把setCount改成useReducer的dispatch,因为useReducer的dispatch 的身份永远是稳定的 —— 即使 reducer 函数是定义在组件内部并且依赖 props

useContext

,useContext肯定与React.createContext有关系的,接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

为什么使用

如果你在接触 Hook 前已经对 context API 比较熟悉,那应该可以理解,useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>。简单点说就是useContext是用来消费context API

如何使用

const?value?=?useContext(MyContext);

知识点合集

useContext造成React.memo 无效

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate??也会在组件本身使用 useContext 时重新渲染

举个例子??:

//?创建一个?context
const?Context?=?React.createContext()
//?用memo包裹
const?Item?=?React.memo((props)?=>?{
??//?组件一,?useContext?写法
??const?count?=?useContext(Context);
??console.log('props',?props)
??return?(
????<div>{count}</div>
??)
})

const?App?=?()?=>?{
??const?[count,?setCount]?=?useState(0)
??return?(
????<div>
??????点击次数:?{?count}
??????<button?onClick={()?=>?{?setCount(count?+?1)?}}>点我</button>
??????<Context.Provider?value={count}>
????????<Item?/>
??????</Context.Provider>
????</div>

??)
}

结果:

可以看到即使props没有变化,一旦组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,此时Memo就失效了

Hooks替代Redux

有了useReduceruseContext以及React.createContext API,我们可以实现自己的状态管理来替换Redux

实现react-redux

react-redux:React Redux is the official React binding for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.

简单理解就是连接组件和数据中心,也就是把React和Redux联系起来,可以看看官方文档或者看看阮一峰老师的文章,这里我们要去实现它最主要的两个API

Provider 组件

Provider:组件之间共享的数据是 Provider 这个顶层组件通过 props 传递下去的,store必须作为参数放到Provider组件中去

利用React.createContext这个API,实现起来非常easy,react-redux本身就是依赖这个API的

const?MyContext?=?React.createContext()

const?MyProvider?=?MyContext.Provider

export?default?MyProvider?//?导出

connect

connect:connect是一个高阶组件,提供了一个连接功能,可用于将组件连接到store,它 提供了组件获取 store 中数据或者更新数据的接口(mapStateToProps和mapStateToProps)的能力

connect([mapStateToProps], [mapStateToProps], [mergeProps], [options])

function?connect(mapStateToProps,?mapDispatchToProps)?{
????return?function?(Component)?{
????????return?function?()?{
????????????const?{state,?dispatch}?=?useContext(MyContext)
????????????const?stateToProps?=?mapStateToProps(state)
????????????const?dispatchToProps?=?mapDispatchToProps(dispatch)
????????????const?props?=?{...props,?...stateToProps,?...dispatchToProps}
????????????return?(
????????????????<Component?{...props}?/>
????????????)
????????}
????}
}

export?default?connect?//?导出

创建store

store: store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

利用useReducer来创建我们的store


?import?React,?{?Component,?useReducer,?useContext?}?from?'react';
import?{?render?}?from?'react-dom';
import?'./style.css';

const?MyContext?=?React.createContext()
const?MyProvider?=?MyContext.Provider;

function?connect(mapStateToProps,?mapDispatchToProps)?{
????return?function?(Component)?{
????????return?function?()?{
????????????const?{state,?dispatch}?=?useContext(MyContext)
????????????const?stateToProps?=?mapStateToProps(state)
????????????const?dispatchToProps?=?mapDispatchToProps(dispatch)
????????????const?props?=?{...props,?...stateToProps,?...dispatchToProps}

????????????return?(
????????????????<Component?{...props}?/>
????????????)
????????}
????}
}

function?FirstC(props)?{
????console.log("FirstC更新")
????return?(
????????<div>
?????????????<h2>这是FirstC</h2>
????????????<h3>{props.books}</h3>
????????????<button?onClick={()=>?props.dispatchAddBook("Dan?Brown:?Origin")}>Dispatch?'Origin'</button>
????????</div>
????)
}

function?mapStateToProps(state)?{
????return?{
????????books:?state.Books
????}
}

function?mapDispatchToProps(dispatch)?{
????return?{
????????dispatchAddBook:?(payload)=>?dispatch({type:?'ADD_BOOK',?payload})
????}
}

const?HFirstC?=?connect(mapStateToProps,?mapDispatchToProps)(FirstC)

function?SecondC(props)?{
???console.log("SecondC更新")
????return?(
????????<div>
????????????<h2>这是SecondC</h2>
????????????<h3>{props.books}</h3>
????????????<button?onClick={()=>?props.dispatchAddBook("Dan?Brown:?The?Lost?Symbol")}>Dispatch?'The?Lost?Symbol'</button>
????????</div>
????)
}

function?_mapStateToProps(state)?{
????return?{
????????books:?state.Books
????}
}

function?_mapDispatchToProps(dispatch)?{
????return?{
????????dispatchAddBook:?(payload)=>?dispatch({type:?'ADD_BOOK',?payload})
????}
}

const?HSecondC?=?connect(_mapStateToProps,?_mapDispatchToProps)(SecondC)


function?App?()?{
????const?initialState?=?{
??????Books:?'Dan?Brown:?Inferno'
????}

????const?[state,?dispatch]?=?useReducer((state,?action)?=>?{
??????switch(action.type)?{
????????case?'ADD_BOOK':
??????????return?{?Books:?action.payload?}
????????default:
??????????return?state
??????}
????},?initialState);
????return?(
????????<div>
????????????<MyProvider?value={{state,?dispatch}}>
????????????????<HFirstC?/>
????????????????<HSecondC?/>
????????????</MyProvider>
????????</div>
????)
}

render(<App?/>,?document.getElementById('root'));

结果:

嗯嗯??,我们就这样实现了一个状态管理

缺陷

  • 缺少时间旅行
  • 不支持中间件
  • 性能极差

可以看到上面的结果,一个状态变化,所有组件都重新渲染,嗯嗯??,所以我们这是个demo玩玩而已,不要用于生产中

最后贴下Redux作者的回答:

useLayoutEffect

useLayoutEffect和useEffect一样也是处理副作用,其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

??官方尽量推荐使用useEffect,因为useLayoutEffect,useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制

区别就是:useEffect是异步的,useLayoutEffect是同步的

为什么使用

解决一些闪烁场景

如何使用


useLayoutEffect(fn,?[])?//?接收两个参数?一个是回调函数?另外一个是数组类型的参数(表示依赖)

知识点合集

??暂无...

自定义hooks

自定义Hooks很简单,利用官方提供的Hook我们可以把重用的逻辑抽离出来,也就是我们的自定义Hook,当你在一个项目中发现大量类似代码,那就抽离成Hooks吧

??前面我们分析了Mixin,HOC,Render Props这些模式来实现状态逻辑复用,这里的自定义hooks也是解决状态逻辑复用问题的一种模式(??终于快完结了)

根据业务来说,我把自定义Hooks分为两类,一类是自定义基础Hooks,另一类是自定义业务Hooks

业务Hooks

比如我们多个页面有相同的请求方法

//?以use开头
export?const?useUserData?=?(category:?string[],?labelName?:?string)?=>?{
??const?[baseTotal,?setBaseTotal]?=?useState<number>(0)

??useEffect(()?=>?{
????dealSearchTotal(category,?labelName)
??},?[labelName,?category])
??const?dealSearchTotal?=?async?(
??)?=>?{
????const?data?=?await?getUserData(curCategory,?labelName)
????const?{?baseTotal,?calculateTotal,?basicTotal,?extTotal?}?=?data
????setBaseTotal(baseTotal)
??}
??//?最后return出想要的数据
??return?[baseTotal,?calculateTotal,?basicTotal,?extTotal]
}

??好如果你注意到你写了重复代码,抽离成自定义Hooks是没问题的

基础Hooks

基础Hooks就是平时与业务无关的工具方法

useEffectOnce

该Hooks在函数组件只执行一次

const?useEffectOnce?=?(effect)?=>?{
??useEffect(effect,?[]);
};

export?default?useEffectOnce;

useMount

该Hook在组件挂载时调用

const?useMount?=?(fn)?=>?{
??useEffectOnce(()?=>?{
????fn();
??});
};

export?default?useMount;

useUnmount

该Hook在组件销毁时调用

const?useUnmount?=?(fn:?()?=>?any):?void?=>?{
??const?fnRef?=?useRef(fn);
??fnRef.current?=?fn;
??useEffectOnce(()?=>?()?=>?fnRef.current());
};

export?default?useUnmount;

usePrevious

获取组件的state或者props的旧值

const?usePrevious?=?(state):?=>?{
??const?ref?=?useRef();
??useEffect(()?=>?{
????ref.current?=?state;
??});
??return?ref.current;
};
export?default?usePrevious;

??其它参考Umi Hooks

最后


相关推荐

go语言,国密SM系列加密算法实现,emmansun/gmsm

多年以来在程序开发中我们进行数据加密的方式都是使用国外的一些加密算法,但是这几年使用国密sm加密算法的越来越多,要求使用sm加密的越来越多了。今天简单介绍下go语言开发中sm系列加密算法的使用。关于s...

每日分享- GO 编程项目装饰器模式怎么实现

在Go语言中实现装饰器模式,可以通过函数闭包或者结构体嵌套的方式来实现。下面分别介绍这两种方式的实现方法和举例说明:1函数闭包方式实现装饰器模式使用函数闭包方式实现装饰器模式,可以将被装饰的函数作为...

IOC-golang 的 AOP 原理与应用

AOP与IOC的关系AOP(面向切面编程)是一种编程设计思想,旨在通过拦截业务过程的切面,实现特定模块化的能力,降低业务逻辑之间的耦合度。这一思路在众多知名项目中都有实践。例如Spring...

go语言并发原语RWMutex实现原理及闭坑指南

1.RWMutex常用方法Lock/UnlockRLock/RUnlockRLocker为读操作返回一个Locker接口的对象2.RWMutex使用方法funcmain(){varc...

使用Go从零实现一个Redis

最近翻阅了几本跟Redis相关的书籍,比如《Redis设计与实现第二版》和钱老师的《Redis深度历险:核心原理与应用实践》,想着Redis的核心功能无非就是操作数据嘛,就像做一个Go语言版的Red...

Go实现独立的Web服务器

一.Web服务器说起web服务器,相信大家都比较熟悉,比如Nginx、Apache、Tomcat等,通过代理或者反向代理方式为用户提供服务。如果使用这些组件,则需要部署Web服务器、项目代码等,而且...

go语言实现优雅退出:graceful shutdown

为什么需要优雅退出?程序又是如何退出的?如果不进行“优雅退出”,任由没有处理的直接退出,会造成什么结果?来看看程序退出时发生了什么,还有如何处理go语言的优雅的退出。程序退出程序运行过程中如果收到了系...

Go语言实现连接MySql基础操作

在Go中,可以使用database/sql包来连接和操作MySQL数据库。以下是一个简单的示例程序,它演示了如何连接MySQL数据库并执行查询操作:packagemainimpo...

go map实现原理

map介绍其实map是一种HashMap,表面上看它只有键值对结构,实际上在存储键值对的过程中涉及到了数组和链表。HashMap之所以高效,是因为其结合了顺序存储(数组)和链式存储(链表)两种存储结构...

Go语言利用反射来实现json序列化和反序列化

代码示例:packagemainimport("encoding/json""fmt""reflect")typePerso...

了解Go语言的map底层实现原理,助力高效编程!

Go语言的map是一种哈希表结构,底层是通过散列表来实现的。每个map元素都包括一个键和一个值,其中键必须是可以进行相等性比较的类型,比如string、int等。在Go语言中,map可以用make函数...

Go语言中如何实现JWT

什么JWTJWT(JSONWebToken)是一种开放标准(RFC7519),定义了一种在各方之间安全传输信息的简洁方式。这些信息可以被验证和信任,因为它们是数字签名的。JWT由三部分组成,用....

Go语言实现一个简单生产者消费者模型

简介:介绍生产者消费者模型,及go简单实现的demo。一、生产者消费者模型生产者消费者模型:某个模块(函数等〉负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、协程、线...

用Go实现一个状态机

工作中,很多同学会用到状态机,例如对一个工单进行创建、编辑、审核,在执行新动作前,要检查能否从当前状态流转到下一个状态。对这种需求,我们怎么实现呢?数组在Go设计模式(22)-状态模式中说过,简单的...

用Go实现网络流量解析和行为检测引擎

摘要能够实现网络协议解析和分析的工具有很多,最有名使用最多的是基于图形化界面的Wireshark,除了能够实现网络实时抓包,还能够离线分析Pcap包文件,虽然它通常用于手动分析网络数据包,但也支...