react dispatch_React系列自定义Hooks很简单

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 时重新渲染

举个例子?:

// 创建一个 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>
  )
}

结果:

8117dddb59bca5553fc3c5c960ec5845.png

可以看到即使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'));

结果:

0ce22f648cb0aed4cd7144723cf14b2c.png

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

缺陷

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

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

最后贴下Redux作者的回答:

9eea6ecc9f4f133b17dceb8ba0d84e5b.png

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

最后

b121e3b5e99442527374ac3f4b0ecaa0.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458000.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端 保存后端传来数据的id_一篇来自前端同学对后端接口的吐槽

前言去年的某个时候就想写一篇关于接口的吐槽&#xff0c;当时后端提出了接口方案对于我来说调用起来非常难受&#xff0c;但又说不上为什么&#xff0c;没有论点论据所以也就作罢。最近因为写全栈的缘故&#xff0c;团队内部也遇到了一些关于接口设计的问题&#xff0c;于是开…

2018-2019-1 《信息安全系统设计基础》教学进程

《信息安全系统设计基础》教学进程 目录 考核方式暑假准备教学进程 第01周学习任务和要求第02周学习任务和要求第03周学习任务和要求第04周学习任务和要求第05周学习任务和要求第06周学习任务和要求第07周学习任务和要求第08周学习任务和要求第09周学习任务和要求第10周学习任务…

Android中的数据库

2019独角兽企业重金招聘Python工程师标准>>> 1.1. 什么时候使用数据库 有大量相似结构的数据需要存储的时候就可以使用数据库。 1.2. SQLite的简介 SQLite是一款轻量级的数据库。它的设计目标是嵌入式的&#xff0c;而且目前已经在很多嵌入式产品中使用了它。Androi…

python计算绩效工资_python实现 --工资管理系统

原博文 2017-07-25 22:41 − # -*- coding: utf-8 -*- __author__ hjianli # import re import os info_message """Alex 100000 Rain 80000 Egon 50000 Yuan 30000 """ #序列字典 xulie_...01669 相关推荐 2019-09-28 21:13 − Python python…

为Windows Server 2012 R2指定授权服务器

为Windows Server 2012 R2指定授权服务器在Windows Server 2008 R2的终端服务中&#xff0c;可以手动指定授权服务器&#xff0c;而在Windows Server 2012 R2中&#xff0c;默认只能通过"远程桌面连接服务"管理器&#xff0c;指定授权服务器&#xff0c;而要使用远程…

spring5高级编程_Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新

简介是什么让java世界变得更好&#xff0c;程序员变得更友爱&#xff0c;秃头率变得不是那么的高&#xff0c;让程序员不必再每天996&#xff0c;有时间找个女朋友&#xff1f;是Spring。是什么让企业级java应用变得简单易懂&#xff0c;降低了java程序员的进入门槛&#xff0c…

关于resolve非泛型方法不能与类型实参一起使用

今天mvc新建三层时&#xff0c;写到bll层中一直报下面的错误&#xff0c;检查了几遍赶脚并没有什么错。最后发现缺少一些引用。 如下面的图&#xff0c;少添加了下面的两个引用.Unity是微软模式与实践团队开发的一个轻量级、可扩展的依赖注入容器, Microsoft.Practices.Unity.C…

设计模式-Singleton

2019独角兽企业重金招聘Python工程师标准>>> Singleton算是知道的设计模式中最简单的最方便实现的了&#xff0c;模式实现了对于类提供唯一实例的方法&#xff0c;在很多系统中都会用到此模式。在实际项目中使用全局变量&#xff0c;或者静态函数等方式也可以达到这…

dump分析工具_Java应用CPU过高,如何排查?参考解决思路和常用工具总结

本文总结了一些常见的线上应急现象和对应排查步骤和工具。分享的主要目的是想让对线上问题接触少的同学有个预先认知&#xff0c;免得在遇到实际问题时手忙脚乱。毕竟作者自己也是从手忙脚乱时走过来的。只不过这里先提示一下。在线上应急过程中要记住&#xff0c;只有一个总体…

dos攻击命令_Kali Linux系列之拒绝服务攻击(DOS)实战(上)

(你的世界是个什么样的世界&#xff1f;你说&#xff0c;我们倾听!)-----------------小百科拒绝服务攻击即是攻击者想办法让目标机器停止提供服务&#xff0c;是黑客常用的攻击手段之一。其实对网络带宽进行的消耗性攻击只是拒绝服务攻击的一小部分&#xff0c;只要能够对目标…

stm32定时器配置

stm32通用定时器 STM32的定时器是个强大的模块&#xff0c;定时器使用的频率也是很高的&#xff0c;定时器可以做一些基本的定时&#xff0c;还可以做PWM输出或者输入捕获功能。 时钟源问题&#xff1a; 名为TIMx的有八个&#xff0c;其中TIM1和TIM8挂在APB2总线上&#xff0c;…

python数组定义_python定义数组

广告关闭 腾讯云11.11云上盛惠 &#xff0c;精选热门产品助力上云&#xff0c;云服务器首年88元起&#xff0c;买的越多返的越多&#xff0c;最高返5000元&#xff01; 一、一维数组 1. 直接定义matrix2. 间接定义matrixprint(matrix)输出&#xff1a;3. 数组乘法matrix*5print…

Android-语言设置流程分析

Android手机语言切换行为&#xff0c;是通过设置-语言和输入法-语言来改变手机的语言&#xff0c;其实这个功能很少被用户使用。 以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:一、语言设置界面&#xff1a;首先在设置app中找到语言设置这个Preference…

charles 安装 ssl_最全面的解决Charles手机抓包的证书问题(步骤非常详细)

源自公众号文章: 彻底解决Charles手机抓包的证书问题简介: Charles 抓包是日常开发当中经常会用到的技术, 在 Android 6 之前, 手机系统既信任系统内置的证书, 也信任用户自己安装的证书, 但是在 Android 7 之后, 却发生了变化, 手机系统只信任系统内置的根证书. 当然了, 这是为…

excel 2007 vba与宏完全剖析_Excel宏VBA小技巧系列 | 分段加合

写在前面的话 知识产权算是一个盛产数据的行业。专利啊商标啊著作啊&#xff0c;都有著录项目。我们常说的专利分析、产业导航、企业导航、产业预警、竞争情报、技术综述、知识产权评议等等&#xff0c;常规操作之一就要先处理著录项目数据&#xff0c;然后再进行不同角度的分…

redhat虚拟机安装

做过好多使用VMware workstation虚拟机搭建的系统&#xff0c;这是我第一次使用Virtual Box&#xff0c;感觉跟Vmware差不多&#xff0c;我的本子的系统是win7 64位。 下面演示安装的是在VirtualBox里安装rhel 6.4 linux 64位系统。 一、VirtualBOX 版本。 二、虚拟机的配置。…

mysql 查看表v空间自增涨_MySQL InnoDB表空间加密

从 MySQL5.7.11开始&#xff0c;MySQL对InnoDB支持存储在单独表空间中的表的数据加密 。此功能为物理表空间数据文件提供静态加密。该加密是在引擎内部数据页级别的加密手段&#xff0c;在数据页写入文件系统时加密&#xff0c;加密用的是AES算法&#xff0c;而其解密是在从文件…

ideaspringboot项目上传服务器_PHP中使用 TUS 协议来实现可恢复文件上传

曾经尝试过用PHP上传大文件吗&#xff1f;想知道您是否可以从上次中断的地方继续上传&#xff0c;而不会在遇到任何中断的情况下再次重新上传整个数据&#xff1f;如果您觉得这个场景很熟悉&#xff0c;请接着往下阅读。文件上传是我们几乎所有现代Web项目中的一项很常见的任务…

java使用xml存储数据_用存储过程和 JAVA 写报表数据源有什么弊端?

用存储过程和 JAVA 写报表数据源有什么弊端&#xff1f;跟着小编一起来一看一下吧&#xff01;我们在报表开发中经常会使用存储过程准备数据&#xff0c;存储过程支持分步计算&#xff0c;可以实现非常复杂的计算逻辑&#xff0c;为报表开发带来便利。所以&#xff0c;报表开发…

MAC OS上JAVA1.6 升级1.7,以及 maven3.2.1配置

一、我的MAC系统 预装的Jdk是1.6&#xff0c;由于需要使用eclipse MARs 2版本&#xff0c;故需要升级到1.7 二、下载JAVA jdk http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html 不知道为什么直接下非常慢&#xff0c;后来用的迅雷就超级快…