近兩年前端技術的發展如火如荼,大量的前端項目都在使用或轉向 Vue 和 React 的陣營, 由前端渲染頁面的單頁應用占比也越來越高,這就代表前端工作的復雜度也在直線上升,前端頁面上展示的信息越來越多也越來越復雜。我們知道,任何狀態都需要進行管理,那么今天我們來聊聊前端狀態管理。
前端狀態管理第三方出名的庫有: Flux、Redux、Vuex、Mobx 等
這里專講react的狀態管理演變
redux
開發者門接觸最多的應該就是redux,這里從淺入深的來逐步學習吧
1 .單純的使用純redux庫,參考redux1
// Action
export const commonType = {//action type
SET_AGE: 'SET_AGE'
}
export function setAge(payload: any) {//action creator
return {
type: commonType.SET_AGE,
payload
}
}
// reducer
const commonReducer = (state = initialState, action: any) =>{
switch (action.type) {
case commonType.SET_AGE:
return { ...state, ...action.payload };
default:
return state;
}
}
const allReducer = combineReducers({
common:commonReducer
})
// store
import { createStore } from 'redux'
let store = createStore(allReducer);
// 使用(訂閱)
import store from '../redux/store'
export default class App extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {iptVal:0};
}
componentDidMount() {
store.subscribe(() =>{
let {common:{age:iptVal}}= store.getState();
this.setState({iptVal});
})
}
render() {
return (<div>{this.state.iptVal}</div>);
}
}
// 使用(廣播)
import store from '../redux/store'
export default class A extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.state = {iptVal:0};
}
componentDidMount() {
store.subscribe(() =>{
let {common:{age:iptVal}}= store.getState();
this.setState({iptVal});
})
}
iptChange = (e: any) =>{
store.dispatch(actions.setAge({
age:e.target.value
}));
}
render() {
const { iptVal } = this.state;
return (
<>
<input type="text" value={iptVal} onChange={this.iptChange} />
<div>{iptVal}</div>
</>
)
}
}
缺點很明顯,需要在改變和監聽數據的地方都引入store,並手動與組件關聯,因此有了第2種方式
2 .使用redux + react-redux方式, 參考redux2
// Action
export const commonType = {//action type
SET_AGE: 'SET_AGE'
}
export function setAge(payload: any) {//action creator
return {
type: commonType.SET_AGE,
payload
}
}
// reducer
import { combineReducers } from 'redux';
const commonReducer = (state = initialState, action: any) =>{
switch (action.type) {
case commonType.SET_AGE:
return { ...state, ...action.payload };
default:
return state;
}
}
const allReducer = combineReducers({
common:commonReducer
})
// store
import { createStore } from 'redux'
let store = createStore(allReducer);
// 使用(訂閱)
import { connect } from 'react-redux'
class App extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
return (<div>{this.props.iptVal}</div>);
}
}
export default connect(
(state: any) => {
return {iptVal: state.common.age}
},null
)(App);
// 使用(廣播)
import { connect } from 'react-redux'
class A extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
return (
<>
<input type="text" value={this.props.iptVal} onChange={this.props.iptChange} />
<div>{this.props.iptVal}</div>
</>
)
}
}
export default connect(
(state: any) => {
return {iptVal: state.common.age}
},
(dispatch: any)=>{return {
iptChange(e: any){
dispatch(actions.setAge({
age:e.target.value
}))
}
}}
)(A);
這樣就不用手動處理全局狀態與react的關系了,如果你了解注解(裝飾器),看起來代碼就更簡單了,反正我是沒有配置成功,你可以試試
不過action creator和reducer創建起來好費勁。action creator要寫大量的重復代碼,reducer遍地的switch case,所以便有了第3種方式。
3 .redux + react-redux + redux-actions, 源代碼在redux3
// Action
import { createAction } from 'redux-actions';
export const commonType = {SET_AGE: 'SET_AGE'};//action type
export const setAge = createAction(commonType.SET_AGE);//action creator
// reducer
import { combineReducers } from 'redux';
import { handleActions } from "redux-actions";
const initialState = {age: 0}; //初始化state
let reducers = {
[commonType.SET_AGE](state: any, action: any){
let { payload } = action;
return {...state,...payload};
}
};
const commonReducer = handleActions<any>(reducers,initialState);
const allReducer = combineReducers({
common:commonReducer
})
// store
import { createStore } from 'redux'
let store = createStore(allReducer);
// 使用(訂閱)
import { connect } from 'react-redux'
class App extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
return (<div>{this.props.iptVal}</div>);
}
}
export default connect(
(state: any) => {
return {iptVal: state.common.age}
},null
)(App);
// 使用(廣播)
import { connect } from 'react-redux'
class A extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
render() {
let {iptVal,iptChange} = this.props;
return (<>
<input type="text" value={iptVal} onChange={iptChange}/>
<div>{iptVal}</div>
</>)
}
}
export default connect(
(state: any) => {
return {iptVal: state.common.age}
},
(dispatch: any)=>{return {
iptChange(e: any){
dispatch(actions.setAge({
age:e.target.value
}))
}
}}
)(A);
這樣做效果已經很好了,至少在hooks來之前,這是大家普遍使用的方法來管理react的全局狀態。但是hooks之后,我推薦如下,原因是 不用引入任何第三方包
React.Context
使用作用域之React.Context,這個學過java的人都知道,此對象是貫穿整個應用的。通過注入便監聽 Context來達到redux同樣的效果,好不用引入第三方包。參考context
// Context
const commonContext = React.createContext({ //初始化,不具體實現
age: 0,
setAge: (age: number) => {}
});
// 使用(注入需要訂閱的組件)
import {useState} from 'react';
import CommonContext from './context';
export default const App = () => {
const [ age, setAges ] = useState(10);
let myValue = {
age,setAge(age: number){setAges(age)}
};
return (<>
<CommonContext.Provider value={myValue}>
<A/>
<B/>
<C/>
<div>{age}</div>
</CommonContext.Provider>
</>);
}
// 使用(發起廣播)
import * as React from 'react';
import { useContext } from 'react';
import commonContext from '../context';
export default const B =()=> {
const commonCtx = useContext(commonContext);
const onChange = (e: any)=>{
commonCtx.setAge(e.target.value)
};
return (<>
<div>{commonCtx.age}</div>
<input type="text" onChange={onChange} value={commonCtx.age}/>
</>)
}
// 使用(訂閱監聽)--函數式組件使用hooks訂閱
import * as React from 'react';
import { useContext } from 'react';
import commonContext from '../context';
export default const A = (props: any) => {
const commonCtx = useContext(commonContext);
return (<div>{commonCtx.age}</div>)
}
// 使用(訂閱監聽)--類組件兩種方式訂閱
import * as React from 'react'
import commonContext from '../context';
export default class C extends React.Component <any,any> {
static contextType = commonContext;
constructor(props: any){
super(props);
}
render(){
return (
// 在沒有useContext的hooks之前,通常這樣取得和監聽Context
<>
方式1:this.context,使用Class.contextType你可以在任何生命周期中訪問到this.context:
<div>{this.context.age}</div>
方式2:Consumer, 讓你在函數式組件中完成訂閱 context:
<commonContext.Consumer>
{commonCtx=><div>{commonCtx.age}</div>}
</commonContext.Consumer>
</>
)
}
};
如果只是用Context,功能能實現,但是還不是很靈活,比如動態的value(state和reducer)你得自己手動創建並關聯,所以便有了如下辦法。
React.Context 和 hooks之useReducer
這是目前react官方最推薦的使用方式,也是本文一路想引申的,如果想單獨看useReducer的使用方式請看useReducer,最終結合版看useReducerContext
// Context
const commonContext: any = React.createContext(null);
//action
export const commonType = {
SET_AGE:'SET_AGE'
}
export const setAge = (payload: any) =>{
return {
type: commonType.SET_AGE,
payload
}
}
//reducer
const initialState: any = {age: 0};
function reducer(state: any, action: any) {
switch (action.type) {
case commonType.SET_AGE:
let { payload } = action;
return {...state,...payload};
default:
throw new Error();
}
}
export {
initialState,
reducer
};
//使用(注入需要訂閱的組件)
import * as React from 'react';
import A from './components/a';
import B from './components/b';
import C from './components/c';
import {useReducer} from 'react';
import { reducer, initialState } from './redux/reducer/common';
import CommonContext from './context';
export default () => {
const myValue = useReducer(reducer, initialState);
return (
<div>
<CommonContext.Provider value={myValue}>
<A/>
<B/>
<C/>
<div>{myValue[0].age}</div>
</CommonContext.Provider>
</div>
);
}
// 使用(發起廣播)
import * as React from 'react';
import { useContext } from 'react';
import commonContext from '../context';
export default const B =()=> {
const [state,dispatch] = useContext(commonContext);
const onChange = (e: any)=>{
let payload = {
age: e.target.value
}
dispatch(setAge(payload))
};
return (<>
<div>{state.age}</div>
<input type="text" onChange={onChange} value={state.age}/>
</>)
}
// 使用(訂閱監聽)--函數式組件使用hooks訂閱
import * as React from 'react';
import { useContext } from 'react';
import commonContext from '../context';
export default const A = (props: any) => {
const [state] = useContext(commonContext);
return (<div>{state.age}</div>)
}
// 使用(訂閱監聽)--類組件兩種方式訂閱
import * as React from 'react'
import commonContext from '../context';
export default class C extends React.Component <any,any> {
static contextType = commonContext;
constructor(props: any){
super(props);
}
render(){
return (
// 在沒有useContext的hooks之前,通常這樣取得和監聽Context
<>
{/* 方式1:this.context,使用Class.contextType你可以在任何生命周期中訪問到this.context */}
<div>{this.context[0].age}</div>
{/* 方式2:Consumer, 讓你在函數式組件中完成訂閱 context */}
<commonContext.Consumer>
{([state]:any)=>{
return <div>{state.age}</div>
}}
</commonContext.Consumer>
</>
//總結:使用useContext()時候我們可以不需要使用Consumer了,看你喜歡哪個了
)
}
};
demo地址:https://gitee.com/dshvv/reactStatus
這只是狀態管理最基本的用法,還有特殊情況 比如異步action等等,沒有專門講,感興趣的可以去看看,不過建議先看最普通和基礎的