useContext,useReducer快速入门

892 阅读4分钟

前言:Hook 是 React 16.8 的新增特性,思想更趋近于函数式编程,用函数声明方式代替class声明方式,无需像class声明组件那样写声明周期,写render函数等

一:useContext:

核心功能:解决父子组件之间传值

下面让我们来回顾一下几种常见的传值方式

1.props传值:

数据是通过 props 属性自上而下(由父及子)进行传递的,组件树的逐层传递 props非常的繁琐

import React, { Component } from 'react'
class Father extends Component {
    render(){
        return(
            <Son value='dark' />
        )

    }
}

function Son(props) {
    return (
        <div>
            < GrandChild value={props.value} />
        </div>
    );
}

class GrandChild extends Component {
    render() {
        return <div>{this.props.value}</div>
    }
}

export default Father

2.Context 传值:

Context 提供了一个无需为每层组件手动添加 props,就能将值深入传递进组件树,避免通过中间元素层层传递 props

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效

import React, { Component } from 'react'

const ThemeContext = React.createContext('light');
class Context extends Component {
    render(){
        return(
            <ThemeContext.Provider value='dark'>
                <Toolbar />
            </ThemeContext.Provider>
         )
    }
}

function Toolbar() {
    return (
        <div>
            <ThemedButton />
        </div>
    );
}

class ThemedButton extends Component {
    render() {
        return(
            <ThemeContext.Consumer>
                {(value) => (
                    <div>{value}</div>
                 )}
            </ThemeContext.Consumer>

        )
     }
 }

export default Context

3.useContext取值

作用: useContext可以很方便去订阅context的改变,读取context的值并渲染在组件上。例如上面的函数式组件中,通过Consumer的形式获取Context的数据,有了useContext可以改写成下面:你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

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

const MyContext = React.createContext(defaultValue);
const value = useContext(MyContext); // useContext参数必须是context对象本身
import React , {useContext , createContext } from 'react'
const CountContext = createContext(0)

function UseContext() {
    return(
        <div>
            <CountContext.Provider value={2}>
            <Counter />
            </CountContext.Provider>
        </div>
    )
}

function Counter(){
    let count = useContext(CountContext);
    return(
        <h2>{count}</h2>
     )
}

export default UseContext

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

二:userReducer

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

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

1.Redux工作原理

Redux 的核心思想之一就是将状态放到唯一的全局对象(一般称为 Store)中,而修改状态则是调用对应的 Reducer 函数去更新 Store 中的状态,大概就像这样:

3BC2CB87-5081-4A3B-AC79-DC55C3F76A7D.png 上图描述的是组件 A 改变 B 和 C 中状态的过程:

  • 三个组件挂载时,从 Store 中获取并订阅相应的状态数据并展示(注意是只读的,不能直接修改)
  • 用户点击组件 A,触发事件监听函数
  • 监听函数中派发(Dispatch)对应的动作(Action),传入 Reducer 函数
  • Reducer 函数返回更新后的状态,并以此更新 Store
  • 由于组件 B 和 C 订阅了 Store 的状态,所以重新获取更新后的状态并调整 UI

reducer示例

function countReducer(state,action){
    switch(action.type){
        case 'add':
           return state+1
        case 'sub':
            return state-1
        default:
            return state
        }
}

2.useReducer的使用方法

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

  1. 第一个参数 reducer 显然是必须的,它的形式跟 Redux 中的 Reducer 函数完全一致,即 (state, action) => newState。
  2. 第二个参数 initialArg 就是状态的初始值。
import { useReducer } from "react";
const initialState = {count: 0};

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        default:
            throw new Error();
      }
}

function UseReducer() {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
          <div>count:{state.count}</div>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>

        </>
     );
}

export default UseReducer

3.第三个参数 init 是一个可选的用于懒初始化(Lazy Initialization)的函数,这样初始 state 将被设置为 init(initialArg)。这也为将来对重置 state 的 action 做处理提供了便利:

import { useReducer } from "react";

function init(initialCount) {
    return {
        count: initialCount
    };
}

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return {count: state.count + 1};
        case 'decrement':
            return {count: state.count - 1};
        case 'reset':
            return init(0)
        default:
            throw new Error();
    }
}

function UseReducer() {
    const [state, dispatch] = useReducer(reducer, 0, init);
    return (
      <>
        <div>count:{state.count}</div>
           <button onClick={() => dispatch({type: 'decrement'})}>减</button>
           <button onClick={() => dispatch({type: 'increment'})}>加</button>
           <button onClick={() => dispatch({type: 'reset'})}>还原</button>
     </>
   );
}

export default UseReducer

其实就是一个简单的读取值和改变值,其实用useState的话一行代码就能搞定

3.什么时候该用 useReducer

useReducer 和 useState 的使用目的几乎是一样的:定义状态和修改状态的逻辑。useReducer 使用起来较为繁琐,但如果你的状态管理出现了至少一个以下所列举的问题:

  • 需要维护的状态本身比较复杂,多个状态之间相互依赖
  • 修改状态的过程比较复杂