# 管理思想
在组件化开发模式中,组件数据共享一直也是复杂应用项目的痛点,基于单向数据流的思想,组件传值变得麻烦,除去父子组件或孙子组件之外,平行组件又或者跨越层级较多的组件之间的通信变的异常繁琐,vue不是很熟悉,应该类似也有API来处理跨多层组件之间的数据共享问题,react下contextAPI可实现跨越多层组件之间的数据共享,但极大的破坏了组件的规范性,使得此类组件很难再进行任何复用,虽然redux增加了项目的复杂度,redux官方也不是很推荐在一些项目中使用react-redux,明确说明-当你还在思考你的项目要不要使用react-redux时,那说明你的项目是不需要使用的,他规定了每个修改数据的动作都要有一个action属性,然后根据action属性进行不同的操作,最终返回一个数据结果,所有有绑定的组件根据最新的状态进行更新。
# 发布订阅
发布订阅模式在任何框架中应用的地方很多,经典的开发思想,MVVM中用来存储更新DOM的操作,数据变更通知所有订阅者更新数据,redux中也不例外,存储有订阅的组件更新视图函数,数据变更时通知所有订阅的组件进行重新渲染(执行render函数),通过一个第三方容器来跨组件管理状态。
# redux
基于所有组件化框架的单向数据流的思想,redux规定我们每次更改数据时都应该有一个action来描述修改数据状态的原因或者动机,从而修改store中的数据,再通知每个订阅者或者组件进行更新视图。
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
# redux示例
抄一波官方文档的代码:
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
react-redux动态更新就是store被dispatch后调用指定的组件进行状态刷新,实现多个组件之间共享状态。
# 简易实现简易的React-Redux
# 效果预览

本章节并不实现react-redux的所有功能,主要是讲解react-redux的设计思想的大概的实现方法。
- forceUpdate:在组件内使用this.forceUpdate()来主动触发视图层更新,组件render完后向发布订阅模式订阅一个更新该组件的函数,修改公共状态时通知订阅者更新视图。
# 文件目录
react-redux
index.js --简易的发布订阅模式
APP.js --create-react-app入口文件
Child.js --订阅者
# index.js
经典发布订阅模式
let state={
name:"CJ"
},arr=[];
let getState=function () {//获取最新的数据
return state;
}
let subscribe=function (fn) {//添加订阅者
arr.push(fn);
}
let updateState=function (callback) {
let newState=callback(state);//执行并传递state参数到callBack函数中
state={...state,...newState};//保留原有参数,仅替换新的状态已有的数据
arr.forEach((item)=>item());//通知订阅者执行
}
export {//导出接口
getState,
subscribe,
updateState
};
# APP.js
添加订阅者函数
import React from 'react';
import {getState,
subscribe,
updateState} from "./react-redux";
import Child from "./Child";
class App extends React.Component{
componentDidMount(){//渲染完成后添加更新的订阅者
subscribe(()=>{this.forceUpdate()});
}
render(){//渲染函数,数据修改时会自动触发视图更新
let {name}=getState();//容器内的state数据
return(
<div>
<h2>跨组件状态共享:</h2>
<h4>订阅的组件:</h4>
Name:{name}
<TestRedux/>
</div>
)
}
}
function TestRedux() {//修改参数
return(
<div>
<Child/>
<input type="text" id='test'/>
<button onClick={()=>{
updateState(state=>{//调用update返回一个最新的状态
return {name:window.test.value}
})
}}>点击修改name</button>
</div>
)
}
export default App;
# Child.js
与容器共享状态
import React from 'react';
import {getState,
subscribe,
updateState} from "./../redux";
class Child extends React.Component {
componentDidMount(){//渲染完成后添加更新的订阅者
subscribe(()=>{this.forceUpdate()});
}
render() {
let {name}=getState();//容器内的state数据
console.log(this.props)
return ( <div style={{background:'orange'}}>
<h4>订阅的组件:</h4>
Name:{name}
</div> );
}
}
export default Child;
# 更好用的MobX
相比react-redux,mobx简洁的语法和神奇的响应式原理,相比react-redux而言项目复杂度降低了许多,在一个大型的react项目中,往往全局状态会有很多,开发者为了尽量减少手动输入字符串标识等,会把一个项目包含react-redux功能的文件拆分为reducer,action-type,action等,使用combineReducers合并成一个reducer,将每个store进行拆分为多个文件分开管理,这种开发方式并不适合一些中小型应用,繁琐复杂的定义和文件引用跳转等,直到后来使用了mobx,一个文件就对应一个state,即全局状态,action使用方法名称来定义,通过对象来调用和派发,即避免了定义常量字符串,也方便开发者使用编辑器的提示调用方法。
# MobX用法
- 使用前请安装mobx
Timer.js
import {observable,action,autorun} from 'mobx';
//observable:创建一个需要被监听的对象
// action:定义触发数据改变函数
// autorrun 数据更改时所执行的函数
var appState = observable({//创建状态
timer: 0
});
appState.increment=action(function reset(){//挂载action函数
appState.timer++
})
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);
autorun(function(){// 数据更改是自动执行的函数
console.log(appState.timer);
})
export default appState;
每次数据修改就会自动运行autorun内的函数并获取到最新的状态值。
# React-MobX
Mobx配合React,像是react-redux一样的,在数据更改时调用autorun方法去触发组件重新渲染。官方使用装饰器模式去包装React组件,被包装的组件在数据修改时可重新渲染最新的视图层数据,也可以使用observer对组件进行包装,效果一样。
import React from 'react';
import Store from './mobx/Timer'// 导入上述的Timer.js文件
import {observer} from 'mobx-react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { };
}
render() {
return (
<div className="App">
{Store.timer}
<button onClick={()=>{
Store.increment()
}}>Increment</button>
</div>
);
}
}
export default observer(App);//渲染包装后的组件
mobx中state数据改变后在React组件中就能及时动态渲染最新状态了。
# 动手实现MobX
MobX基于ES6新增API-Proxy实现,用来对数据添加中间层代理,弥补了基于Object.definePrototype的缺陷。
- observable:返回被proxy代理的对象,每次触发set时调用autorun内的函数执行
- observer:包装react组件,触发set时调用autorun执行组件更新。
- autorun:数据改变时执行的函数。
# 文件目录
mobx
action.js action函数触发事件执行改变状态
autorun.js 数据改变时执行的函数
mobx-react.js 增加组件刷新功能
observable.js 数据增加代理
# mobx-react.js
import autorun from "./autorun";
export default function observer(Component) {//包装的组件增加autorun自动刷新
autorun( ()=> {
Component.prototype.componentWillMount =function () {//组件加载完成调用原型方法自动重新渲染
autorun(() => {
this.forceUpdate();
});
}
});
return Component
}
# observable.js
import autorun from "./autorun";
export default class observable{
constructor(target){
return new Proxy(target,{//返回代理后的proxy对象
get(target,key,val){
return target[key]
},
set(target,key,val){
if(target[key]!==val){
target[key]=val;//数据更改促发autorun函数执行
autorun(autorun.target)
return val
};
}
})
}
}
# autorun.js
export default function autorun(fn) {
if(fn){
autorun.target=fn;//函数赋值给target对象,proxy内可以取到
fn();
}
}
使用react-app渲染
import React from 'react';
import {observer,observable} from "./mobx";
//导入包装组件和代理
class App extends React.Component {
render() {
return(
<div>
<h2>{store.name}</h2>
<SubApp/>
<input onChange={(event)=>{state.name=event.target.value}}/>
</div>
)
}
}
class SubApp extends React.Component {
render() {
return(
<div>
<h2>{store.name}</h2>
</div>
)
}
}
export default observer(App);
//input的onchage事件改变state的数据,通知组件重新渲染。
自此,一个基本的mobx-react的功能就完成了,mobx的大致运行原理也更深入了一层。
# redux-saga
# 简介
最近看一些招聘要求上React全家桶又多了一个,起初好奇的去看了看,主要还是处理我们常见的异步全局状态,功能类似redux-thank,个人感觉前端很多代码和书写方式都没有说有一种特定的要求,使用react-redux或者mobx在异步数据获取后进行dispatch也是一种异步处理,如果非要说saga的好处的话,就是使用了Generator 函数的功能,相比之后的async/await,Generator似乎对异步的管控会更自由,异步代码调用也越来越像同步代码了。代码地址
# 开始
redux-saga也是基于redux的,所以基本的代码几乎都是一样的,下面时一个简单的计数器使用saga去控制状态。
reducer.js
export default function counter(state = 0, action) {//传递当前的状态值/
//以及action的信息{至少拥有一个type属性说明本次更新的类型}
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'INCREMENT_IF_ODD':
return (state % 2 !== 0) ? state + 1 : state
case 'DECREMENT':
return state - 1
default:
return state
}
}
Counter.js
import React, { Component, PropTypes } from 'react'
//计时器组件,接收value,同步增加减少,异步增加减少的dispatch函数
const Counter = ({ value, onIncrement, onDecrement, onIncrementAsync,onDecrementAsync }) =>
<div>
<button onClick={onDecrementAsync}>
Decrement after 1 second
</button>
{' '}
<button onClick={onIncrementAsync}>
Increment after 1 second
</button>
{' '}
<button onClick={onIncrement}>
Increment
</button>
{' '}
<button onClick={onDecrement}>
Decrement
</button>
<hr />
<div>
Clicked: {value} times
</div>
</div>
Counter.propTypes = {
value: PropTypes.number.isRequired,
onIncrement: PropTypes.func.isRequired,
onDecrement: PropTypes.func.isRequired,
}
export default Counter
sagas.js
import { delay } from 'redux-saga'
import { put, takeEvery, all } from 'redux-saga/effects'
function* incrementAsync() {
yield new Promise((reslove)=>{
setTimeout(()=>{
reslove('success!!!')
},2000)
})
yield put({ type: 'INCREMENT',res:'test' })
}
function* decrementAsync(){
yield new Promise((reslove)=>{
setTimeout(reslove,1000);
})
yield put({type:'DECREMENT',msg:'DECREMENT'})
//执行store.dispatch({type:'DECREMENT',msg:'DECREMENT'})
}
// watcher Saga: 在每个 INCREMENT_ASYNC action 派生一个新的 incrementAsync 任务
function* watchIncrementAsync() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync);
}
function* watchDecrementAsyc(){
yield takeEvery('DECREMENT_ASYNC',decrementAsync)
}
// notice how we now only export the rootSaga
// single entry point to start all Sagas at once
export default function* rootSaga() {
yield all([
decrementAsync(),
incrementAsync(),
watchIncrementAsync(),
watchDecrementAsyc(),
])
}
main.js(入口文件)
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './Counter';
import reducer from './reducers';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
//创建saga中间件
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)//使用redux使用中间件
)
sagaMiddleware.run(rootSaga);//启动异步saga
const action = type => store.dispatch({type})//派发行为
function render() {
ReactDOM.render(
<Counter //传递同步异步行为
value={store.getState()}
onIncrement={() => action('INCREMENT')}
onDecrement={() => action('DECREMENT')}
onIncrementAsync={() => action('INCREMENT_ASYNC')}
onDecrementAsync={() => action('DECREMENT_ASYNC')} />,
document.getElementById('root')
)
}
render()
store.subscribe(render)//绑定视图层,数据变更做出对应的响应(刷新视图)
实现效果就是四个按钮,每个按钮点击绑定各自的同步异步事件,各自按照各自的时间触发实现。个人理解还是较浅。
# 动手实现saga
还早还早,Generator 底层原理还没开始接触,先去看看再回来试试。