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完成登录场景

    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完成登录场景

    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([])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 值由上层组件中距离当前组件最近的 的 value prop 决定。

为什么使用

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

如何使用

const value = useContext(MyContext);

知识点合集

useContext造成React.memo 无效

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

举个例子?:

// 创建一个 contextconst 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没有变化,一旦组件上层最近的 更新时,该 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>这是FirstCh2><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>这是SecondCh2><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(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

最后


react dispatch_React系列自定义Hooks很简单相关推荐

  1. 自定义Toast 很简单就可以达到一些对话框的效果 使用起来很方便

    自定义一个layout布局 通过toast.setView 设置布局 弹出一些警示框 等一些不会改变的提示框 很方便 public class CustomToast { public static ...

  2. react dispatch_React纯Hooks状态管理探索

    第一次发前端的文章,好紧张 React从16.8开始支持Hooks,社区也出现了大量好用的轮子 而我最看好的两个有关状态管理的轮子 jamiebuilds/unstated-next​github.c ...

  3. 利用React 自定义Hooks实现业务逻辑复用实例

    目录 简介 适用读者 请求数据的例子 需求 定义 States 加载 Posts 加载 Todos 展示数据 问题 用自定义 Hooks 改造 useRequest 加载 Posts 和 Todos ...

  4. python123鸡兔同笼编程_用编程的方法解决“鸡兔同笼”系列问题,其实很简单

    原标题:用编程的方法解决"鸡兔同笼"系列问题,其实很简单 今日题型讲解 同学们是不是觉得古人很"无聊"啊,有事没事竟然把鸡和兔子关在一个笼子里数腿玩,还要各种变 ...

  5. 英特尔核显自定义分辨率_核显也能60帧玩3A大作?原理很简单!8寸电脑掌机游戏测评...

    现在科技发展迅速,电脑硬件每年更新换代,性能也是越来越强大,处理器自带的核芯显卡性能也早已超过了普通人的理解范围,相信有很多玩家也早就领教过了英特尔HD系列核显的强大性能,轻轻松松玩个英雄联盟之类的网 ...

  6. mysql入门很简单系列视频-学习笔记

    mysql入门很简单系列视频-学习笔记 视频链接:mysql入门很简单系列视频 https://www.bilibili.com/video/av14920200/ 以前主要就了解DDL.DML.DC ...

  7. 【Youtobe trydjango】Django2.2教程和React实战系列六【自定义主页、路由与请求】

    [Youtobe trydjango]Django2.2教程和React实战系列六[自定义主页.路由与请求] 1. 初始化主页应用 2. 修改视图 3. 项目url增加主页路由 4. 路由 1. 初始 ...

  8. 其它React Hooks以及自定义Hooks和第三方Hooks库的使用

    - useMemo 作用:相当于vue中的计算属性.用于组件中复杂运算的优化 特点:当它所关联的声明式变量发生变化,它才会重新运算:反之,不会 语法:const result = useMemo(fn ...

  9. 优化自定义函数_10分钟教你手写8个常用的自定义hooks

    前言 Hook 是 React 16.8 的新增特性.它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性.本文是一篇以实战为主的文章,主要讲解实际项目中如何使用ho ...

最新文章

  1. 程序模拟抽奖流程图_一道与联欢会相关的概率统计题目的模拟仿真
  2. Linux文件基本属性
  3. [20161219]关于LANGUAGE_MISMATCH.txt
  4. mysql可以靠索引,而我只能靠打工,加油,打工人!
  5. 项目经理如何理解定位技术
  6. 用Cython加速Python代码,快到起飞!
  7. DbgPrint 格式字符串
  8. python机器学习案例系列教程——支持向量机SVM、核函数
  9. hightcharts 3d 堆积图下钻
  10. 爬取mm131套图并下载到本地
  11. 使用粒子群PSO算法实现MPPT-M语言仿真
  12. 武田宣布美国FDA授予ALUNBRIG® (brigatinib)作为ALK+转移性非小细胞肺癌一线治疗的补充新药申请优先审理
  13. html5快速制作,html5动画制作(教你如何快速绘制HTML5动画)
  14. ‘home‘ has a default child route. When navigating to this named route (:to=“{name: ‘home‘“), the def
  15. 苹果开发者账号添加设备
  16. C++中流控制函数 setw() setfill() setbase() setprecision()
  17. 【CV】FPN:用于目标检测的特征金字塔网络
  18. java bearer token_从Web API在MVC中存储Bearer Token的位置
  19. 仿微信通讯录滑动定位ListView功能
  20. 衣服、商品、商城网站模板首页,仿U袋网,vue+elementui简洁实现(二)

热门文章

  1. VisualCode 查看代码历史版本、还原代码到既定历史版本
  2. 官方文档: Dubbo 框架设计、模块说明、依赖关系
  3. SpringMVC 注解 : @ModelAttribute
  4. Linux find和grep的区别
  5. dubbo ,dubbo-provider、dubbo-consumer 配置参数说明
  6. 表单验证的初步实现和省市级联
  7. 外籍主管眼中的阿里巴巴
  8. 推荐系列文章:《DotText源码阅读》
  9. @Java | Thread synchronized - [ 线程同步锁 基本使用]
  10. [UWP]了解模板化控件(4):TemplatePart