手把手教你用useReducer实现一个redux(typescript版)

818 阅读4分钟

引言

React的v16.8新推出了钩子hook特性,让前端小朋友们能够更加愉快的使用函数式编程来编写你的组件。当然了,如果你更喜欢面向对象的编程方式,也可以仍然使用Class。本篇文章手把手教大家做一个小demo,用useReducer来撸一个伪redux。

建议朋友们先熟悉一下useState,useEffect和useReducer的原理,这里是门钥匙

正题

熟悉react的同学都知道,redux是react生态下的状态管理器,用来管理组件之间的状态共享,而不需要一层一层的传递props。

redux没有使用什么黑魔法,如果你之前使用过,理解起本篇来简直so easy。之前没有接触过的小伙伴,这里再给个门钥匙

话不多说,咱们先看看如何使用吧。

首先,useReducer接收两个参数,第一个参数是reducer处理函数,第二个是初始的state对象。返回一个是最新的state对象,第二个是dispatch函数,这个dispatch用来触发reducer,我们接下来会介绍到。

const [state, dispatch] = useReducer(likeReducer, initState)

我们来看看我们要传入的两个参数具体是怎么写的。

首先是初始化的state,是一个对象:

const initState = {
  like: 0, // 点赞次数
  jet: 0 // 刷火箭次数
}

然后是reducer函数,接收state和action,其中state是旧的state,第一次useReducer会将初始值initState传递过来;action包含type和payload。

// 你也可以将action像redux那样单独提取出来 也可以不提 这里为了方便 没有提取

// 定义了一个点赞reducer

function likeReducer (state: any, action: any) {
  switch (action.type) {
    case 'addLike': // 添加赞
      return {
        ...state,
        like: state.like + 1
      }
    case 'addJet': //刷火箭
      return {
        ...state,
        jet: state.jet + action.payload
      }
    case 'desLike': // 减少赞
      return {
        ...state,
        like: state.like - 1
      }
    default:
      return state
  }
}

看到这里,你就知道了,useReducer做的,就是将初始的state传递给reducer,并且返回state和dispatch函数。

每次触发dispatch之后,他都会更新新的state返回。

这里需要注意的是,我们使用了es6的解构符来创建新的state,朋友们不要直接修改原state,即使使用Object.assign()的时候也记得把第一个设置成{},因为如果比较发现state没有更新,系统不会触发相关组件的重新渲染。

现在可以试试使用了

const Button: React.FC= () => {
  const [state, dispatch] = useReducer(likeReducer, initState)
  const addClick = () => {
    dispatch({type: 'addLike'})
  }
  const desClick = () => {
    dispatch({type: 'desLike'})
  }
  return (
    <div>
        <button onClick={() => {addClick()}}>加赞:{state.like}</button>
        <button onClick={() => {desClick()}}>减赞:{state.like}</button>
        <button onClick={() => {dispatch({type: 'addJet',payload: 3})}}>刷3支火箭:{state.jet}</button>
    </div>
  )
}

你在想,这不就是把组件内的useState改成useReducer嘛?说好的父子组件间共享呢?别急,共享状态涉及到作用域,我们来引入一个作用域。

import { useContext } from "react";

然后在父组件的文件里,创建这个作用域。

interface IContextProps {
  state: {
    like: number;
    jet: number
  };
  dispatch: ({type}:{type:string}) => void;
}
const LikeContext = React.createContext({} as IContextProps)

我们把Button作为父组件,改造一下:

const Button: React.FC= () => {
  const [state, dispatch] = useReducer(likeReducer, initState)
  const addClick = () => {
    dispatch({type: 'addLike'})
  }
  const desClick = () => {
    dispatch({type: 'desLike'})
  }
  return (
    <LikeContext.Provider value={{state, dispatch}}> // 加入作用域提供者,将我们的state和dispatch都传递给子组件
        <button onClick={() => {addClick()}}>加赞:{state.like}</button>
        <button onClick={() => {desClick()}}>减赞:{state.like}</button>
        <button onClick={() => {dispatch({type: 'addJet',payload: 3})}}>刷3支火箭:{state.jet}</button>
        <FontUi /> // 子组件
    </LikeContext.Provider>
  )
}

再写一下子组件:

const FontUi: React.FC = (props) => {
    const {state, dispatch} = useContext(LikeContext) // 这里就用useContext拿到了提供者提供的值 注意 子组件或孙组件需要包含在提供者里
    return <button onClick={() => {dispatch({type: 'addLike'})}}>子组件dispatch{state.like}</button> // 可以调用dispatch并实时改变作用域内的state
}

就可以试试我们的伪redux啦!这里演示了父子组件的状态共享及修改,你自己也可以用孙组件尝试一下,放出demo全部码

import React, { useEffect, useContext, useReducer } from "react";

const initState = {
  like: 0,
  jet: 0
}
interface IContextProps {
  state: {
    like: number;
    jet: number
  };
  dispatch: ({type}:{type:string}) => void;
}
const LikeContext = React.createContext({} as IContextProps)

function likeReducer (state: any, action: any) {
  switch (action.type) {
    case 'addLike':
      return {
        ...state,
        like: state.like + 1
      }
    case 'addJet':
      return {
        ...state,
        jet: state.jet + action.payload
      }
    case 'desLike':
      return {
        ...state,
        like: state.like - 1
      }
    default:
      return state
  }
}

const Button: React.FC= () => {
  const [state, dispatch] = useReducer(likeReducer, initState)
  const addClick = () => {
    dispatch({type: 'addLike'})
  }
  const desClick = () => {
    dispatch({type: 'desLike'})
  }
  return (
    <LikeContext.Provider value={{state, dispatch}}>
      <button onClick={() => {addClick()}}>加赞:{state.like}</button>
      <button onClick={() => {desClick()}}>减赞:{state.like}</button>
      <button onClick={() => {dispatch({type: 'addJet',payload: 3})}}>刷3支火箭:{state.jet}</button>
      <FontUi />
    </LikeContext.Provider>
  )
}

const FontUi: React.FC = (props) => {
  const {state, dispatch} = useContext(LikeContext)
return <button onClick={() => {dispatch({type: 'addLike'})}}>子组件dispatch{state.like}</button>
}

export default Button

文章写的不易,转载请注明出处。转载回传传送门