![a1a448a5df663f8a3b0bb1f1eb7126f0.png](https://i-blog.csdnimg.cn/blog_migrate/0ce5a5c0593571c80ec9217064361d0a.png)
在新context API出来的时候,就已经有人提出可以用context进行状态管理。react-hooks的更新,使得这一想法的实现更为方便。以一个例子组件进行分析。
前置条件:阅读过react官方文档中,context这一 节与hooks这一 章。
中文版:context hooks
需求
我们需要实现这样一个组件:
- 内容可变的,也就是说,具有state
- 内容的变化是组件自己更新的,譬如:一个按钮与一段文本组成的组件,点击一下按钮,文本内容便多出一段
- 该组件的可变内容,可以被外部组件(非子组件)获取。第二点的例子里,文本内容可以被其他组件读取到。
- 该组件暴露内容的方式尽量简单
分析
这是很常见的需求,尤其是在jQuery时期,是一种复用性极高的组件。实现的思路也很简单:
第一种方法,按照纯react目前提供的数据流,父组件(外部组件)将方法作为该组件的props传进来。此处不提。
第二种方法,该组件有一个全局变量,保存了state。当该组件被复用时,将该组件的全局变量merge到整个APP的全局变量上。APP的其他组件通过访问全局变量来访问该组件的state。
第二种方法很容易让人想到redux等状态管理库。但实际上,context + react-hooks已经基本能满足需求。这里分享的是思路,而不是一个新lib的说明书。我也一直以为,可实践的思路远比一个实现好的库有用。
实现
全局变量选定是context。与redux相仿,为了数据流可控,这里也采用reducer
接收dispatch(action)
,从而引起store
(这里是context
)变化,这样的数据流。
假设我们要实现的组件叫Editor
定义action
Editor/actionTypes.js
export const ADD_PARA = 'ADD_PARA'
Editor/actions.js
import { ADD_PARA } from './actionTypes'
export const addPara = para => ({
type: ADD_PARA,
para,
})
编写reducer
Editor/reducer.js
import { ADD_PARA } from './actionTypes'
import { composeReducer } from '../utils'
const reducer1 = (state, action) => {
switch (action.type) {
case ADD_PARA: {
return [action.para, ...state]
}
default: {
return state
}
}
}
const reducer = composeReducer(reducer1)
//自己实现一个工具方法,可以合并reducer,当然这里只有一个reducer
export default reducer
hooks实现context的api
Editor/context.js
这个文件里,我们编写需要暴露给外部的api。因为依赖context,实际上能够暴露出去的只有三个api:Provider
、Consumer
、Context
。
作为createContext()
返回的Context
对象,本身具有Provider
、Consumer
两个属性。Provider
的value
属性的值应为context的初始值,而且必须在此设值。
这里有一个小小的坑点;createContext()
方法的参数,并不是初始值,其参数仅仅是为了在Provider
不存在的情况下初始化context,英文文档这里写得比较明确。所以实际意义上的initValue
应该在Provider
的value
属性设值。
ThedefaultValue
argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passingundefined
as a Provider value does not cause consuming components to usedefaultValue
.
useReducer
方法可以基于给定reducer
返回dispatch
方法
将全局变量的值与改变值的方法一并作为context传入。这样使得Editor组件本身可以改变自己的state
基于以上原因与需求第四点,如下实现:
import React, { createContext, useReducer } from 'react'
import reducer from './reducer'
const EditorContext = createContext()
const initContext = []
//封装一下Provider,为其设初始值
const EditorProvider = props => {
const [state, dispatch] = useReducer(reducer, initContext)
return (
<EditorContext.Provider value={{ state, dispatch }}>
{props.children}
</EditorContext.Provider>
)
}
const EditorConsumer = EditorContext.Consumer
export { EditorContext, EditorConsumer, EditorProvider }
view
Editor/view.js
实现逻辑非常简单,所以这里暂不区分容器组件与傻瓜组件
useContext
方法可以获取context的值,并在context变动时重新渲染
import React, { useContext } from 'react'
import { EditorContext } from './context'
import { addPara } from './actions'
const Editor = props => {
const { state, dispatch } = useContext(EditorContext)
return (
<>
{state.map(content => (
<p key={content + Math.random()}>{content}</p>
))}
<button onClick={() => dispatch(addPara('gggg'))}>add</button>
</>
)
}
export { Editor }
导出
Editor/index.js
export { Editor } from './view'
export { EditorContext, EditorProvider, EditorConsumer } from './context'
这样,一个满足要求的Editor组件就实现完成了,该组件由一个文本区域与一个button组成,每次点击button,文本区域会增加一个值为gggg
的p
段落
调用
App.js
调用该组件,实际上是把导出的Provider
作为共同父组件即可
import React from 'react'
import { Editor, EditorProvider } from './Editor'
import Exp from './Exp'
import './App.css'
const App = props => (
<EditorProvider className="App">
<Editor />
<Exp />
</EditorProvider>
)
export default App
Exp/index.js
import React, { useContext } from 'react'
import { EditorContext } from '../Editor'
const Exp = () => {
const { state } = useContext(EditorContext)
return (
<>
{state.map(content => (
<p key={content + Math.random()}>{content}</p>
))}
</>
)
}
export default Exp
如上,我们把另一个需要访问该组件值的组件Exp
与该组件Editor
的共同父组件作为Provider
。Exp
可以很方便的访问Editor
的state
以上就使用react-hooks与context完成了一个简单的,易复用的react组件。
实现该组件的目的,不仅仅是满足需求,也是为了看到hooks与context相结合,作状态管理的潜力。
友情提示:
目前(2019.02.03),能够使用react-hooks的react版本"react": "^16.8.0-alpha.1",
"react-dom": "^16.8.0-alpha.1"
更多内容,可以参考Facebook的redux-react-hook:它是另一种思路,保留redux,用context与hooks代替react-redux。