More than code
Thoughts, stories and ideas

使用 React Hooks + Context 打造简版 Redux

一、前言

一般我们写 React 如果不是特别大的应用,前后端数据交互逻辑不复杂,这样我们直接按照正常流程写组件就能满足简单的业务场景。随着业务场景的深入渐渐地我们组件变大变多,组件与组件之间的数据通讯(也就是状态管理,不过我更愿意称之为数据通讯)变得越来越复杂。所以我们引入了 Redux 来维护我们日趋复杂的数据通讯。

二、思路

秉承着这种思路,我在开发应用的时候是没有一开始就引入 Redux ,因为一开始我觉得就是个小项目。随着深入项目的开发,其实并没有这么简单。

但是也没有太复杂,这时我把眼光放到了 Context 身上。Context 本意是上下文,它提供一个 Provider 和一个 Consumer,也就是生产者/消费者模式,在某个顶层提供一个 Provider ,下面的子元素通过 Consumer 来消费 Provider 里的数据和方法。

通过这个概念,我们把不同层级里的组件共享同一个顶层 Provider,并且组件内部使用 Consumer 来消费共享数据。

当我们能共享数据后,还剩一个问题就是如何更改 Provider 里的数据呢?答案是:useReducer

好,有了思路,我们来实现一下。

三、实例

假设我们在某一个层级有个需要共享状态的父级元素,我们称它为 Parent,在 Parent 下面不同层级之间有两个 Child。这里为了简单举例假设两个Child内都是共同的逻辑。

import React from "react"

function Parent() {
  const colors = ['red', 'blue']
  return (
    <>
      <Child1 color={colors[0]} />
      <Child2 color={colors[1]} />
    </>
  )
}

function Child1(props) {
  return (
    <div style={{ background: props.color }}>I am {props.color}</div>
  )
}

function Child2(props) {
  return (
    <div style={{ background: props.color }}>I am {props.color}</div>
  )
}


我们现在已经构造出了这样的一个上下级结构,目前通过给子组件传递属性,可以实现父组件的状态共享。但是这里如果层级加深,我们传递属性的层级也要跟着加深。这样显然不是我们想要的。

现在我们来引入 Context

首先通过 createContext 方法初始化我们需要的 Context

import React, { createContext } from "react"

const Context = createContext({
  colors: ['red', 'blue']
})

然后我们在 Parent 和 Child 里引入刚才的 Context,并且使用 useContext 拿到共享的数据:

import React, { useContext, createContext } from "react"

const Context = createContext({
  colors: []
})

function Parent() {
  const initState = {
    colors: ["red", "blue"]
  }

  return (
    <Context.Provider value={{ colors: initState.colors }}>
      <>
        {/* 假装这些地方有着不同的层级 */}
        <Child1 />
        <Child2 />
      </>
    </Context.Provider>
  )
}

function Child1(props) {
  const { colors } = useContext(Context);

  return (
    <div style={{ background: colors[0] }}>
      I am {colors[0]}
    </div>
  )
}

// 省略 Child2 代码,同 Child1 一致

现在只是拿到了数据并且进行渲染,再进一步,通过点击元素,修改颜色。在这里我们就需要用 useReducer 来模拟触发改变。

首先我们需要一个 reducer 来处理触发的改变。


function reducer(state, action) { const { colors } = action if (action.type === "CHANGE_COLOR") { return { colors: colors } } else { throw new Error() } }

这里我简化了 action 的处理,当然你也可以进行扩展。

现在,我们给 Provider 加上提供改变的方法 dispatch

import React, { useContext, createContext } from "react"

const Context = createContext({
  colors: []
})

function Parent() {
  const initState = {
    colors: ["red", "blue"]
  }

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

  return (
    <Context.Provider value={{ colors: state.colors, dispatch: dispatch }}>
      <>
        {/* 假装这些地方有着不同的层级 */}
        <Child1 />
        <Child2 />
      </>
    </Context.Provider>
  )
}

然后子组件触发改变:


function Child1(props) { const { colors, dispatch } = useContext(Context) return ( <div style={{ background: colors[0] }} onClick={() => dispatch({ type: "CHANGE_COLOR", colors: ["yellow", "blue"] }) } > I am {colors[0]} </div> ) } // 省略 Child2 代码,同 Child1 一致

至此,这个小型的状态共享便完成了。这便是我们摆脱 Redux 之后实现的状态共享思路的雏形。

四、进阶

在实际的应用中,我们的业务场景会更复杂,比如我们的数据是动态获取的。

这种情况下你可以把 Provider 抽出来,当 Parent 数据回来之后再初始化 Context


import React, { useState, useEffect } from "react" function Parent (props) { const [data, setData] = useState() const [url, setUrl] = useState('https://example.com') useEffect(() => { fetch(url).then(res => setData(data)) }, [url]) if (!data) return <div>Loading ...</div> return ( <Provider colors={data}> <> {/* 假装这些地方有着不同的层级 */} <Child1 /> <Child2 /> </> </Provider> ) }

然后我们在 Parent 中做异步操作,并把动态数据传给 Provider :


import React, { useState, useEffect } from "react" function Parent (props) { const [data, setData] = useState() const [url, setUrl] = useState('https://example.com') useEffect(() => { fetch(url).then(res => setData(data)) }, [url]) if (!data) return <div>Loading ...</div> return ( <Provider colors={data}> <> {/* 假装这些地方有着不同的层级 */} <Child1 /> <Child2 /> </> </Provider> ) }

五、深入

我们可以更进一步,让我们的状态管理机制更加精简。

首先,在某个组件层级定义我们需要的 Context 。假如,我们这里是在顶层(也就是全局的状态管理)。

import React from 'react'

// 创建我们需要的 Context
export const AppContext = React.createContext(null)

然后我们将 useReducer 的返回值直接传给 AppContext.Provider。

import React, { useReducer } from 'react'

// 全局 Provider
export function AppProvider ({reducer, initValue, children}) {
  return (
    <AppContext.Provider value={useReducer(reducer, initValue)}>
      {children}
    </AppContext.Provider>
  )
}

最后,添加一个自定义 hooks 来获取 AppContext 里的状态和方法。Write Once, Run Anywhere 🙂

import React, { useReducer, useContext } from 'react'

export const useAppState = () => useContext(AppContext)

最后我们的 state.js 完整代码如下:

import React, { useContext, useReducer } from 'react'

export const AppContext = React.createContext(null)

export function AppProvider ({reducer, initValue, children}) {
  return (
    <AppContext.Provider value={useReducer(reducer, initValue)}>
      {children}
    </AppContext.Provider>
  )
}

export const useAppState = () => useContext(AppContext)

组件里使用:

import { AppProvider, useAppState } from "./state"

function App() {
  const initState = {
    colors: ["red", "blue"]
  }

  function reducer(state, action) {
    const { colors } = action;
    if (action.type === "CHANGE_COLOR") {
      return { colors: colors };
    } else {
      throw new Error();
    }
  }

  return (
    <AppProvider initValue={initState} reducer={reducer}>
      <div>
        {/* 假装这些地方有着不同的层级 */}
        <Child1 />
        <Child2 />
      </div>
    </AppProvider>
  )
}

function Child1(props) {
  const [state, dispatch] = useAppState()

  return (
    <div
      style={{ background: state.colors[0] }}
      onClick={() =>
        dispatch({
          type: "CHANGE_COLOR",
          colors: ["yellow", "blue"]
        })
      }
    >
      I am {state.colors[0]}
    </div>
  )
}

六、结语

这样小型的状态管理机制你甚至可以放在某个组件里,而不用放到如 Redux 全局的环境中去。这样使得我们写的应用更加灵活,而不是一味的往 store 里丢状态。当然你也可以写一个 AppProvider 来管理全局的状态,React Hooks + Context 给了我们这样的便利。

七、原文

https://juejin.im/post/5d5501cd6fb9a06aee362a9d

赞(2) 打赏
未经允许不得转载:Saxon's blog » 使用 React Hooks + Context 打造简版 Redux

相关推荐

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

React CLI - 快速创建react项目脚手架工具

官 网GitHub

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏