使用 React Hooks + Context 打造简版 Redux

原文地址:github.com/hentaicrack…javascript

React Hooks 在 React@16.8 版本正式发布。我最近在一两个公司的内部项目中也开始用起来尝尝鲜。html

不了解 Hooks 的同窗先撸一遍文档。本文不对 Hooks 作详细介绍,只阐述一种使用 Hooks 的思路。vue

通常咱们写 React 若是不是特别大的应用,先后端数据交互逻辑不复杂,这样咱们直接按照正常流程写组件就能知足简单的业务场景。随着业务场景的深刻渐渐地咱们组件变大变多,组件与组件之间的数据通信(也就是状态管理,不过我更愿意称之为数据通信)变得愈来愈复杂。因此咱们引入了 Redux 来维护咱们日趋复杂的数据通信。java

思路

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

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

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

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

好,有了思路,咱们来实现一下。后端

实例

假设咱们在某一个层级有个须要共享状态的父级元素,咱们称它为 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 以后实现的状态共享思路的雏形。完整的代码及例子见 tiny redux

进阶

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

这种状况下你能够把 Provider 抽出来,当 Parent 数据回来以后再初始化 Context

function Provider (props) {
  const { colors } = props
  const initState = {
    colors,
  }
  const [state, dispatch] = useReducer(reducer, initState)

  return (
    <Context.Provider value={{ colors: state.colors, dispatch: dispatch }}> {props.children} </Context.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>
  )
}

复制代码

结语

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

Hooks 真香!

相关文章
相关标签/搜索