用过react的人都知道,当业务和交互逻辑变得复杂时,仅仅使用react会使得数据维护越来越复杂,组件之间共享数据时需要不断通过HOC(高阶组件)和render props的方式来解决,显然这种方式是不合理的,使得代码中出现了越来越多不必要的组件和逻辑;为了解决这个问题facebook推出了flux,它是一种架构思想,它是一种解决数据流通的方案;
具体思想就是单一数据源,把所有的需要跨组件共享的数据以及修改数据的方法都放在store中集中管理,需要使用共享数据的组件只需要获取store中的数据源,触发更改数据的action,dispatcher接收到action更新store中的数据,并触发store的onchange事件,组件监听到数据发生变化后重新获取新的数据进行渲染;
核心概念view,action,dispatcher,store
具体可以参考阮一峰老师的文章http://www.ruanyifeng.com/blog/2016/01/flux.html
补充
flux中可以有多个store
1:redux是将flux和函数式编程Elm结合在一起的一个状态管理库。
2:核心概念
state,action,reducer
state用来存放单一的数据源;
reducer用来更新state,并且是纯函数形式的更新,这样每个时刻的state都能够保存下来,时光机和撤销得以实现;
import { createStore, combineReducers} from 'redux';
//combineReducers参数对象中的key值对应着state中每个key的值,比如下面的state的返回值为{defaultState: {...}, firstState: {...}, secondState: {...}}
const rootReducer = combineReducers({
defaultState,
firstState,
secondState,
});
//initState是初始化state的值;
const store = createStore(rootReducer, initState);
3:redux的api
4:三大原则
5:补充
需要单独安装react-redux
npm install --save react-redux
1:通过容器组件和展示组件来实现,展示组件通过props获取和展示数据,通过connect将展示组件变成容器组件,容器组件通过mapStateToProps和mapDispatchToProps获取和更改state数据;
为了使得所有的容器组件能够访问到state数据
类组件写法
import { connect, Provider } from "react-redux";
import { createStore } from 'redux';
import reducers from './reducer.js';
const store = createStore(reducers);
<Provider store="store">
<App /><!-- 子组件放在App下 -->
</Provider>
类组件写法
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {};
}
@connect(mapStateToProps, mapDispatchToProps)
export default class Tes1 extends React.PureComponent{
render() {}
}
@connect()
export default class Test2 extends React.PureComponent{
render() {
console.log(this.props.dispatch); //此处的dispatch就是全局的dispatch
}
}
函数式组件写法
import { useDispatch, useSelector } from 'react-redux';
//hooks组件
const Test3 = (props) => {
const state = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>123</div>
);
}
export default Test1;
//普通函数组件
const Test4 = ({dispatch}) => {
return (
<div>123</div>
);
}
export default connect()(Test2);
2:异步数据流处理
react-redux是不能处理异步数据的,目前可以通过中间件的方式解决,
以redux-thunk举例
redux-thunk介绍:
redux-thunk可以让你dispatch一个函数,该函数参数为(dispatch, getState),通过thunk.withExtraArgument可以注入额外的参数,比如:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument(api))
)
// actions
function fetchUser(id) {
return (dispatch, getState, api) => {
// you can use api here
}
}
3:总体用法介绍
import React from 'react';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { createStore, combineReducers, applyMiddleware } from 'redux';
//reducers
function loadReducer (state, action) {
switch(action) {
case 'updateLoadStatus':
return {
...state,
loadStatus: action.loadStatus
}
break;
default:
break;
}
}
function dataReducer (state, action) {
switch(action) {
case 'updatePageData':
return {
...state,
pageData: action.pageData
}
break;
default:
break;
}
}
const rootReducer = combineReducers({loadReducer, dataReducer});
//actions
const actions = {
fetchData: (params) => (dispatch, getState) => {
return fetch(url, params).then(res => {
dispatch({
type: 'updatePageData',
pageData: res.data
});
})
}
};
const loggerMiddleware = createLogger();
const store = createStore(rootReducers, applyMiddleware(thunkMiddleware, loggerMiddleware));
const reducers = combineReducer();
//同步调用
store.dispatch({
type: 'updateLoadStatus',
loadStatus: 'loading'
})
//异步调用
store.dispatch(actions.fetchData({pageIndex: 0}))
.then(() => {console.log(store.getState())})
4:react-redux的api
1:redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。
这意味着应用的逻辑会存在两个地方:
Sagas 是通过 Generator 函数来创建的,因为使用了 Generator,redux-saga 让你可以用同步的方式写异步代码。
Sagas 不同于 Thunks,Thunks 是在 action 被创建时调用,而 Sagas 只会在应用启动时调用(但初始启动的 Sagas 可能会动态调用其他 Sagas)。 Sagas 可以被看作是在后台运行的进程。Sagas 监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。
2:使用介绍
参考文章 https://chenyitian.gitbooks.io/redux-saga/content/
3:总结
使用 Effect 诸如 call 和 put,与高阶 API 如 takeEvery 相结合,让我们实现与 redux-thunk 同样的东西, 但又有额外的易于测试的好处。
1:目前使用react最流行的数据流解决方案需要引入多个库,比较麻烦,dva将 react-router + react-redux + redux-saga3个工具库包装在一起,简化了api,使得开发更加快捷方便。
dva通过model将处理异步流程的effect和同步更新state的reducer, 以及订阅数据源的subscriptions封装在一起
2:使用介绍
import dva from 'dva';
const app = dva({
initialState: {},//初始state,优先级高于model中的state
history,//指定给路由用的history,默认是hashHistory
onError,//effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。
onAction,//action被dispath时触发,用于注册redux中间件
onStateChange,//state改变时触发
onReducer,//封装全局的reducer
onEffect,//封装全局的effect
onHmr,//
extraReducers,//额外的reducer
extraEnhancers,//额外的effect
});
app.model(model);//注册model
const model = {
namespace: 'xxx',
state: {},
effects: {
// 自动执行next的generator函数
*effect({payload}, {call, put, select}) {
// 用于获取所有model的state
const todos = yield select(state => state.todos);
yield call(异步函数, 异步函数参数);
yield put({
type: '',//同一个model,直接写reducer的名字,跨model需要加上命名空间'命名空间/reducer名',
payload
})
}
},
reducers: {
reducer(state, aciton) {
return {};
}
},
subscriptions: {
setup({ history, dispatch }, done) {
// 监听 history 变化,当进入 `/` 时触发 `load` action
return unlistenFunction;//取消数据订阅,app.unmodel()必须取消数据订阅
},
}
}
app.use();//配置 hooks(opts 里也可以配所有的 hooks) 或者注册插件。(插件最终返回的是 hooks )
app.unmodel(namespace);//注销model
app.router(() => {})//单页注册路由表,多页返回jsx元素
// 单页
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
return (
<Router history={history}>
<Route path="/" component={App} />
</Router>
);
});
// 多页
app.router(() => <App />)
app.start('#root');//启动应用
最后通过connect将数据和组件串联起来,connect的组件通过props可以直接获取到dispatch,通过mapStateToProps获取所有model的数据。
补充:
browserHistory和hashHistory的区别:
browserHistory:路由跳转的时候通过改变path来实现,比如"https://www.baidu.com/a" -> “https://www.baidu.com/b”,浏览器会想服务器请求新的页面资源,需要服务器做特殊的处理使得path改变的时候不新增页面
hashHistory:路由跳转的时候通过改变hash来实现,浏览器不会发起新的请求,只是改变了hash值,通过window.addEventListener(‘hashchange’,function(e) {/** e.oldURL,e.newURL / },false);监听hash值的变化
3:总结
解决了react没有解决的几个问题:
1:组件之间如何通信**
2:数据如何和视图串联起来,异步数据如何处理,路由和数据如何绑定?
本文是本人作为个人笔记所用,后面会不断更新完善;如有想一起学习进步的小伙伴,欢迎交流