React学习笔记——Hooks中useReducer和useContext的组合使用替代redux

在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state以及其他的 React 特性
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)

1、Hook 使用规则

Hook 就是JavaScript 函数,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者嵌套函数(子函数)中调用。
2、只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
3、在多个useState()调用中,渲染之间的调用顺序必须相同

2、计数器实例
(1)基础版

DemoContext.js (暴露一个Context的方法)

import React , {createContext}from 'react'

const DemoContext = createContext()

export default DemoContext

App.js (上级组件)

import React , {useReducer} from 'react';
import DemoContext from './DemoContext'
import App2 from './App2'
const App = (props = {}) => {
    const initialState = {num1:0, num2:0, num3:0}
    const reducer = (state,action) => {
        // console.log('reducer' ,state,action)
        switch(action.type){
            case 'num1Add':
                return Object.assign({},state,{num1 : state.num1 + 1})
            case 'num2Add':
                return {
                    ...state,
                    num2 : state.num2 + 1
                }
                
            case 'num3Add':
                return{
                    ...state,
                    num3:state.num1 + state.num2
                }
            case 'numAdd':
                return{
                    ...state,
                    num1:state.num1+2
                }
            default :
                return state
        }
    }
    const [state,dispatch]  = useReducer(reducer,initialState)
    return (
        <div style={{backgroundColor:'yellow'}}>
            爷爷
            <button onClick={()=>{dispatch({type:'numAdd'})}}>爷爷的按钮</button>
            <DemoContext.Provider value={{state,dispatch}}>
                <App2></App2>
            </DemoContext.Provider>
        </div>
    );
};

export default App;

(注意reducer里面返回的写法不同)

App2.js (中间组件)

import React from 'react';
import App3 from './App3'
const App2 = () => {
    return (
        <div style={{backgroundColor:'greenYellow'}}>
            儿子
            <App3></App3>
        </div>
    );
};

export default App2;

App3.js (使用该状态组件)

import React, { useContext ,useMemo , memo} from 'react';
import DemoContext from './DemoContext'
const App3 = memo((props = {}) => {
    const {state,dispatch} = useContext(DemoContext)
    return (
        console.log('孙子render'),
        <div>
            孙子
            <div>num1 : {state.num1}</div>
            <div>num2 : {state.num2}</div>
            <div>num3 : {state.num3}</div>
            <div>
                <button onClick={()=>{dispatch({type:'num1Add'})}}>按钮1</button>
                <button onClick={()=>{dispatch({type:'num2Add'})}}>按钮2</button>
                <button onClick={()=>{dispatch({type:'num3Add'})}}>按钮3</button>
            </div>
        </div>
    );
});

export default App3;

效果图:
在这里插入图片描述
(可以看出,我们使用useReducer + useContext 的组合,能够在子组件里面能够获取到上级组件传递过来的状态,并且能够进行修改

不过发现,在子组件中点击“按钮3”的时候,虽然state.num1 + state.num2并没有改变,但是一直触发子组件render,即使子组件是通过 React.memo 包装过的。

(2)useMemo() 优化

既然 React.memo() 无法拦截注入到 Context 的 state 的变化,那就需要我们在组件内部进行更细粒度的性能优化,这个时候可以使用 useMemo()

App3.js
(去掉了 React.memo,在 return 内部通过 useMemo() 包装,并且声明了所有依赖项)

import React, { useContext ,useMemo  , memo} from 'react';
import DemoContext from './DemoContext'
const App3 = (props = {}) => {
    const {state,dispatch} = useContext(DemoContext)
    return useMemo(()=>{
        console.log('孙子render')
        return (
            <div>
                孙子
                <div>num1 : {state.num1}</div>
                <div>num2 : {state.num2}</div>
                <div>num3 : {state.num3}</div>
                <div>
                    <button onClick={()=>{dispatch({type:'num1Add'})}}>按钮1</button>
                    <button onClick={()=>{dispatch({type:'num2Add'})}}>按钮2</button>
                    <button onClick={()=>{dispatch({type:'num3Add'})}}>按钮3</button>
                </div>
            </div>
        );
    },[state.num1,state.num2,state.num3])
};

export default App3;

优化后的效果图:
在这里插入图片描述
从上面效果可以发现,当 state.num1 + state.num2 不变的时候,是不会触发 return 中 DOM 的重新渲染的。

3、颜色改变实例(网络)

状态管理

//ColorContext.js
import React ,{createContext,useReducer} from 'react';

const ColorContext = createContext({})

const reducer = (state,action) => {
    console.log('reducer',state)
    switch(action.type) {
        case 'UPDATE_COLOR':
          return action.color
        default:
          return state  
    }
}
const Color = (props) => {
    const [color, dispatch] = useReducer(reducer, 'blue')
    return (
        <div>
            <ColorContext.Provider value={{color, dispatch}}>
                {props.children}
            </ColorContext.Provider>
        </div>
    );
};

export  {
    Color,
    ColorContext
};

总index.js组件

import React from 'react';
import ShowArea from './ShowArea'
import Buttons from './Buttons'
import {
    Color
} from './ColorContext'

const Demo1 = () => {
    return (
        <Color>
            <ShowArea />
            <Buttons />
        </Color>
    );
};

export default Demo1;

按钮Button.js

import React ,{useContext}from 'react';
import {ColorContext} from './ColorContext'

const Button = (props) => {
    const {dispatch} = useContext(ColorContext)
    return (
        <div>
            <button onClick={()=>{dispatch({type:'UPDATE_COLOR',color:'red'})}}>红色</button>
            <button onClick={()=>{dispatch({type:'UPDATE_COLOR',color:'yellowGreen'})}}>绿色</button>
        </div>
    );
};

export default Button;

颜色显示ShowArea.js

import React , {useContext}from 'react';
import {ColorContext} from './ColorContext'
const ShowArea = (props) => {
    const {color} = useContext(ColorContext)
    return (
        console.log('颜色显示组件',color),
        <div style={{color: color}}>
            字体颜色展示为blue
        </div>
    );
};

export default ShowArea;

效果图:
在这里插入图片描述

4、总结
  • useContext 创建全局状态,不用一层一层的传递状态
  • useReducer 创建 reducer,并根据不同的 dispatch 更新 state
  • 代码写到哪里状态就加到哪里,不用打断思路跳到 redux 里面去写。
  • 全局状态分离,避免项目变大导致 Redux 状态树难以管理。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值