Hook
是React 16.8
的新特性,它可以讓在不編寫class
類組件的情況下使用state
以及其他的React
特性;而Context
是React16.3
版本里面引入新的Context API
,在以往React
版本中存在一個(gè)Context API
,那是一個(gè)幕后試驗(yàn)性功能,官方提議避免使用,Redux
的原理就是建立在舊的Context API
。現(xiàn)在新的Context ApI
提供了一個(gè)無需為每層組件手動(dòng)添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法,為數(shù)據(jù)通訊另辟蹊徑。
Context簡介
為解決多層嵌套不同層級(jí)組件之間props
數(shù)據(jù)傳遞,這種數(shù)據(jù)傳遞及其繁雜,而且后期不易進(jìn)行維護(hù),為避免driling
式數(shù)據(jù)通訊,可以采用redux
進(jìn)行數(shù)據(jù)通訊。在新版本React 16.8.6
中Context
為我們帶來新的通訊方式。
Context API
組成部分
React.createContext
函數(shù):創(chuàng)建context
上下文,參數(shù)是一個(gè)默認(rèn)值(需要傳遞state
數(shù)據(jù)),state
可以是Object、Array
或者基本類型數(shù)據(jù)。Provider
:由React.createContext
創(chuàng)建返回對(duì)象的屬性。在Redux vs. The React Context API中比喻成構(gòu)建組件樹中的電子總線
比較形象。Consumer
:由React.createContext
創(chuàng)建返回對(duì)象的屬性。比喻接入電子總線
獲取數(shù)據(jù)。
Context
vs redux
Context
的context.Provider/Context.Consumer
和redux
的provider/connect
非常相似。Context
采用的是生產(chǎn)者消費(fèi)者的模式,我們可以利用高階函數(shù)(Hoc
)模擬實(shí)現(xiàn)一個(gè)redux
。
redux
是通過dispatch
一個(gè)action
去修改store
數(shù)據(jù);在React 16.8.6
版本的React hooks
提供的useredcuers
和useContext
為我們更方便通過Context+hooks
的形式去打造一個(gè)屬于自己redux
Context
簡單例子
Context
設(shè)計(jì)目的是為了共享那些對(duì)于一個(gè)組件樹而言是“全局”的數(shù)據(jù),例如當(dāng)前認(rèn)證的用戶、主題或首選語言。
Class.contextType
掛載在class
上的 contextType
屬性會(huì)被重賦值為一個(gè)由 React.createContext()
創(chuàng)建的 Context
對(duì)象。這能讓你使用 this.context
來消費(fèi)最近 Context
上的那個(gè)值。你可以在任何生命周期中訪問到它,包括render
函數(shù)中。
Context.Consumer
讓你在函數(shù)式組件中完成訂閱 context
。這需要函數(shù)作為子元素(function as a child)這種做法。這個(gè)函數(shù)接收當(dāng)前的 context
值,返回一個(gè) React
節(jié)點(diǎn)。傳遞給函數(shù)的value
值等同于往上組件樹離這個(gè) context
最近的Provider
提供的 value
值。如果沒有對(duì)應(yīng)的 Provider
,value
參數(shù)等同于傳遞給createContext()
的 defaultValue
。
// Context 可以讓我們無須明確地傳遍每一個(gè)組件,就能將值深入傳遞進(jìn)組件樹。
// 為當(dāng)前的 theme 創(chuàng)建一個(gè) context(“l(fā)ight”為默認(rèn)值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一個(gè) Provider 來將當(dāng)前的 theme 傳遞給以下的組件樹。
// 無論多深,任何組件都能讀取這個(gè)值。
// 在這個(gè)例子中,我們將 “dark” 作為當(dāng)前的值傳遞下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 讀取當(dāng)前的 theme context。
// React 會(huì)往上找到最近的 theme Provider,然后使用它的值。
// 在這個(gè)例子中,當(dāng)前的 theme 值為 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
// 也可以按照這種形式獲取
function ThemedButton(){
return (
<ThemeContext.Counsumer>
{theme =>(
<Button theme={theme} />
)
}
</ThemeContext.Counsumer>
);
}
context
的詳細(xì)用法可以參考 Context文檔
React Hooks
React Hooks
是React 16.8.6
版本為函數(shù)式組件添加了在各生命周期中獲取state
和props
的通道??勺屇诓痪帉戭惖那闆r下使用 state(狀態(tài)) 和其他 React 功能。不再需要寫class
組件,你的所有組件都將是Function
。如果想了解更多關(guān)于React hooks
信息可以參考Hooks API 參考。
基礎(chǔ)鉤子API
-
useState
:獲取組件state
狀態(tài)數(shù)據(jù),第一個(gè)參數(shù)是保存的數(shù)據(jù),第二參數(shù)是操作數(shù)據(jù)的方法,類似于setState
??捎?code>ES6的數(shù)組解構(gòu)賦值來進(jìn)行獲取。 -
useEffect
: 網(wǎng)絡(luò)請(qǐng)求、訂閱某個(gè)模塊、DOM
操作都是副作用,useEffect
是專門用來處理副作用的。在class
類組件中,componentDidMount
和componentDidUpdate
生命周期函數(shù)是用來處理副作用的。 -
useContext
:useContext
可以很方便去訂閱context
的改變,并在合適的時(shí)候重渲染組件。例如上面的函數(shù)式組件中,通過Consumer
的形式獲取Context
的數(shù)據(jù),有了useContext
可以改寫成下面:
function ThemedButton(){
const value = useContext(ThemeContxet);
return (
<Button theme={value} />
);
}
useReducers API
如果習(xí)慣了redux
通過reducer
改變state
或者props
的形式,應(yīng)該比較很好上手useReducers
,useReducers
和useContext
是這篇文章比較重點(diǎn)的API
。
-
useReducers
:useReducers
可以傳入三個(gè)參數(shù),第一個(gè)是自定義reducer
,第二參數(shù)是初始化默認(rèn)值,第三個(gè)參數(shù)是一個(gè)函數(shù),接受第二個(gè)參數(shù)進(jìn)行計(jì)算獲取默認(rèn)值(可選)。
const [state,dispatch] = useReducer(reducer,initialValue)
下面是useReducers
官方示例:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'reset':
return initialState;
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
// A reducer must always return a valid state.
// Alternatively you can throw an error if an invalid action is dispatched.
return state;
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, {count: initialCount});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'reset'})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
簡易版Redux
redux
具有Provider
組件,通過store
進(jìn)行傳值。在這里,我們使用Context
模擬實(shí)現(xiàn)Provider
和store
傳值,完整代碼可以參考 simple-redux。
封裝Provider
組件
代碼中的storeContext
是React.createContext()
函數(shù)創(chuàng)建的Context對(duì)象。this.props.store
是模擬通過store
傳值操作。
import React,{Component} from 'react';
import {storeContext} from './store';
export default class Provider extends Component{
render(){
return (
<storeContext.Provider value={this.props.store}>
{this.props.children}
</storeContext.Provider>
)
}
}
store數(shù)據(jù)管理
store
文件,包括reducer
、Context
的創(chuàng)建,initialState
與reducers
的定義。
import React from 'react';
export const storeContext = React.createContext();
export const initialState = {
user:'kiwis',
age:23
}
export const reducer = (state, action)=>{
switch (action.type) {
case 'CHANGENAME':
return {user:'harhao',age:24}
default:
return initialState;
}
}
App.js入口
在根組件App.js
中,使用React hooks
d的useReducer
鉤子函數(shù),返回更改state
的dispatch
函數(shù)。然后把store
數(shù)據(jù)和dispatch
傳遞進(jìn)封裝的Provider
組件中。
import React,{useReducer} from 'react';
import Provider from './views/Provider';
import Child from './views/child';
import {initialState as store,reducer} from './views/store';
import './App.css';
function App() {
const [state,dispatch] = useReducer(reducer,store);
return (
<div className="App">
<Provider store={{state,dispatch}}>
<Child/>
</Provider>
</div>
);
}
export default App;
Child子組件
在App.js
的子組件Child
中,通過useContext
獲取傳遞的數(shù)據(jù)state
和dispatch
。在redux
中通過connect
高階函數(shù)來傳遞數(shù)據(jù)。這里可以在useContext
外包裹一層函數(shù),更好模擬實(shí)現(xiàn)與connect
相似的語法。
import React,{useContext} from 'react';
import {storeContext} from './store';
import DeepChild from './deepChild';
function Child() {
const {state,dispatch}= useContext(storeContext);
return (
<div className="child">
<p>姓名:{state.user}</p>
<p>年齡:{state.age}</p>
<button onClick={()=>dispatch({type:'CHANGENAME'})}>changeName</button>
<p>deep child:</p>
<DeepChild/>
</div>
);
}
export default Child;
DeepChild(孫組件)
在Child
子組件中,引入DeepChild
組件。通過useContext
獲取頂層最近的state
數(shù)據(jù)。
import React,{useContext} from 'react';
import {storeContext} from './store';
export default function DeepChild(){
const {state} = useContext(storeContext);
return (
<div>
{state.user}
</div>
)
}
運(yùn)行效果
child
子組件和DeepChild
孫組件通過useContext
獲取頂層數(shù)據(jù),最終運(yùn)行效果如下所示:
如果喜歡可以給個(gè)贊或星喲
git地址:https://github.com/Harhao/simple-redux
參考文章
React中文文檔
[譯]2019 React Redux 完全指南
[譯] Redux vs. React 的 Context API
React Hooks 解析(上):基礎(chǔ)
React Hooks 解析(下):進(jìn)階