React redux 原理及使用详解
作者:空山与新雨 发布时间:2023-07-14 15:41:58
概述
一个状态管理工具
Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
State:包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
Action:Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
Reducer:Store 收到 Action 以后,必须给出一个新的 State。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
dispatch:是 View 发出 Action 的唯一方法。
整个工作流程:
首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
State 一旦有变化,Store 就会调用监听函数,来更新 View。
三大原则
1.单一数据源(Store) 整个应用的State被存放在一棵Object tree中,并且这个Object tree只存在唯一一个Store中;
2.State是只读的 唯一改变 State 的方法就是触发 Action,Action 是一个用于描述已发生事件的普通对象。 确保了所有的修改都能被集中化处理。
3.通过纯函数Reducer来修改Store, Reducer 只是一些纯函数,它接收先前的 State 和 Action,并返回新的 State。 即reducer(state, action) => new state
createStore创建store
createStore 方法接受 3 个参数参数 (reducer, [preloadedState], enhancer);
返回 store,store 上挂载着 dispatch、getState、subscribe、replaceReducer 等方法第二个参数是 preloadedState,它是 state 的初始值,实际上他并不仅仅是扮演着一个 initialState 的角色,如果我们第二个参数是函数类型,createStore 会认为传入了一个 enhancer,如果我们传入了一个 enhancer,createStore 会返回 enhancer(createStore)(reducer, preloadedState)的调用结果,这是常见高阶函数的调用方式。
enhancer 接受 createStore 作为参数,对 createStore 的能力进行增强,并返回增强后的 createStore。然后再将 reducer 和 preloadedState 作为参数传给增强后的 createStore,最终得到生成的 store。
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 第二个参数是一个函数,没有第三个参数的情况
enhancer = preloadedState;
preloadedState = undefined;
}
// 如果第三个参数是函数走下面的逻辑,返回一个新的createStore
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
// enhancer 不是函数就报错
throw new Error('Expected the enhancer to be a function.');
}
// enhancer就是高阶函数,强化了本身这个createStore的函数,拿到增强后的createStore函数去处理
// applyMiddleware这个函数还会涉及到这个
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
// reducer不是函数报错
throw new Error('Expected the reducer to be a function.');
}
// 其他代码省略
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
};
}
applyMiddleware 应用中间件
返回一个函数 enhancer;
右边中间件的执行时机由左边的中间件决定,这是因为 next 的方法的调用时机由右边控制
dispatch 的处理使用了闭包,这样保证在中间件中调用 dispatch 也是用的最终的 dispatch,也是同一个 dispatch;
写中间件的时候如果要调用 dispatch,一定要有跳出条件,防止死循环
export default function applyMiddleware(...middlewares) {
return (createStore) =>
(...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.',
);
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
}
// 其实就是修改了dispatch
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
combineReducers 合并多个reducer
从执行结果看,这时候 state 已经变成了一个以这些 reducer 为 key 的对象;
reducer 也变成了一个合并的 reducer;
遍历执行所有的 reducer 的时候把 action 传进去,返回新的 state;
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers);
const finalReducers = {};
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
const finalReducerKeys = Object.keys(finalReducers);
/* 返回一个整合后的reducers */
return function combination(state = {}, action) {
let hasChanged = false;
const nextState = {};
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
const previousStateForKey = state[key];
const nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action);
throw new Error(errorMessage);
}
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
dispatch
默认的 action 只能是普通对象,除非使用了第三方中间件,比如 redux-thunk
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.',
);
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?',
);
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = (currentListeners = nextListeners);
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
中间件
Redux 中间件在发起一个 action 至 action 到达 reducer 的之间提供了一个第三方的扩展。本质上通过插件的形式,将原本的 action->redux 的流程改变为 action->middleware1->middleware2-> ... ->reducer,通过改变数据流,从而实现例如异步 Action、日志输入的功能。
Redux 中间件范式
一个中间件接收 store 作为参数,会返回一个函数
返回的这个函数接收老的 dispatch 函数作为参数(一般用 next 作为形参),会返回一个新的函数
返回的新函数就是新的 dispatch 函数,这个函数里面可以拿到外面两层传进来的 store 和老 dispatch 函数
function logger(store) {
return function (next) {
return function (action) { // 新的 dispatch 函数
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result;
};
};
}
中间件的调用顺序
首先调用顺序和书写顺序是一致的,但是这里面的洋葱模型包含了两次顺序,从洋葱出来的顺序是和书写顺序相反
import React from 'react';
import { createStore, applyMiddleware } from 'redux';
function createLogger({ getState, dispatch }) {
return (next) => (action) => {
const prevState = getState();
console.log('createLogger1');
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;
console.log(`%c prev state`, `color: #9E9E9E`, prevState);
console.log(`%c action`, `color: #03A9F4`, action);
console.log(`%c next state`, `color: #4CAF50`, nextState);
return returnValue;
};
}
function createLogger2({ getState }) {
return (next) => (action) => {
const console = window.console;
const prevState = getState();
console.log('createLogger2');
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;
console.log(`%c prev state2`, `color: #9E9E9E`, prevState);
console.log(`%c action2`, `color: #03A9F4`, action);
console.log(`%c next state2`, `color: #4CAF50`, nextState);
return returnValue;
};
}
const reducer = function (state = { number: 0 }, action) {
switch (action.type) {
case 'add':
return {
number: state.number + action.number,
};
default:
return state;
}
};
const store = createStore(
reducer,
applyMiddleware(createLogger, createLogger2),
);
store.subscribe(function () {
console.log(1111);
});
const { dispatch } = store;
const App = () => {
const handler = () => {
dispatch({ type: 'add', number: 10 });
};
return (
<div>
<button onClick={handler}>触发redux</button>
</div>
);
};
export default App;
store
store 的属性如下:
dispatch: ƒ dispatch(action)
getState: ƒ getState()
replaceReducer: ƒ replaceReducer(nextReducer)
subscribe: ƒ subscribe(listener)
redux 数据流
Redux 的数据流是这样的:
界面 => action => reducer => store => react => virtual dom => 界面
bindActionCreators
将action对象转为一个带dispatch的方法
比如connect接收的mapDispatchToProps 是对象,会使用 bindActionCreators 处理; 接收 actionCreator 和 dispatch,返回一个函数;
function bindActionCreator(actionCreator, dispatch) {
// 返回一个函数
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
const mapDispatchToProps = { // actionCreators 这是个集合,
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
转换为:
const mapDispatchToProps = { // actionCreators 这是个集合,
onClick:function(filter) {
return dispatch({ // dispatch 是闭包中的方法
type: 'SET_VISIBILITY_FILTER',
filter: filter
})
}
}
compose
函数套函数,compose(...chain)(store.dispatch)结果返回一个加强了的 dispatch;
这点和koa比较相似,这个 dispatch 在执行的时候会调用中间件。
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 每一次reduce迭代都会返回一个加强版的dispatch
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args)),
);
}
加强版 dispatch(一个方法,接收 action 参数),在中间件中用 next 表示,执行 next 之后,会形成一个链条。
enhancer
enhancer,翻译过来就是 store 加强器,比如 applymiddleware 的返回值就是一个 enhaner。store 加强器可以重新构建一个更强大的 store,来替换之前的基础版的 store,让你的程序可以增加很多别的功能,比如 appllymiddleware 可以给你的 redux 增加中间件,使之可以拥有异步功能,日志功能等!
// 以createStore为参数
(createStore) =>
(...args) => {};
使用 redux 常遇见的问题
样板代码过多 增加一个 action 往往需要同时定义相应的 actionType 然后再写相关的 reducer。例如当添加一个异步加载事件时,需要同时定义加载中、加载失败以及加载完成三个 actionType,需要一个相对应的 reducer 通过 switch 分支来处理对应的 actionType,冗余代码过多;
目前已经存在着非常多的解决方案,比如dva redux-tookit等。
更新效率问题:由于使用不可变数据模式,每次更新 state 都需要拷贝一份完整的 state 造成了内存的浪费以及性能的损耗。
其实 redux 以及 react-redux 中内部已做优化,开发的时候使用 shouldComponentUpdate 等优化方法也可以应用,也可以用不可变数据结构如 immutable、Immr 等来解决拷贝开销问题。
来源:https://www.cnblogs.com/walkermag/p/16930528.html
猜你喜欢
- 通常我们在制作上图的时候,会分别给四个div加上不同的css属性,来实现中间间隔。但我们更希望的是不需要对html标签做标识,直接能通过cs
- <% dim conn,mdbfile mdbfile=server.mappath("数据库名称.mdb") s
- --SQL正则替换函数 代码如下:CREATE function dbo.regexReplace ( @source ntext, --
- 注意:安装时要保证Oracle安装目录不能带有中文字符(如果第一次安装出现“加载数据库错误areasQueries”的错误,一般是因为Ora
- 我使用“ Web 2.0设计”来形容目前占主导优势的网页设计风格, 很多人用这个词来形容:网络经济的复苏网站和用户之间更高水平的交互或一种社
- 长期以来我就有对几年来交互设计的心得进行总结整理的想法。回到中国来亲身体会到不少同行,主要是交互设计师和视觉设计师对于交互设计的困惑,以及其
- 最近在内部讨论关于”完美三栏”的话题,看到一篇”In Search of the Holy Grail“,相当的好.故此翻译之.In Sea
- 无论是公司的同事还是外界的程序员朋友们,大部分人对JavaScript的高级应用不甚了解,已有的知识架构里会认为JavaScript仅仅是一
- 一、定位 oracle分两大块,一块是开发,一块是管理。开发主要是写写存储过程、触发器什么的,还有就是用Oracle的Develop工具做f
- 问题:MySQL 5.0不能使用自动增加字段。具体原因:引起的原因是在MySQL5.0.2版本中的The Server SQL Mode 中
- 做网站数据库,是选SQL Server还是Access好,可能您会说:选MySQL好,不过现在只是讨论IIS+ASP这种架构下的选择,不讨论
- 还是网站在不同操作系统不同浏览器下兼容性的问题,但难度加了一层.如果是要检查用户登录后的页面的兼容性,该怎么办?现在一般的测试网站,都是提交
- 在开发Web应用时,无一例外地需要访问数据库,以完成对数据的查询、插入、更新、删除等操作。受应用逻辑的影响,有时需要将多条数据库操作指令组成
- 对于数据库管理员来说,可以经常想了解一些之前未听说过的存储过程,特别是无文档类型的存储过程。或许是用这些存储过程,能够简化日常的数据管理。为
- 平时我们获取事件对象一般写法如下:function getEvent(event) { return e
- 这段时间在处理SQL server 2000 SP4补丁打不上的问题上花了不少时间,回头想想应该总结一下:系统说明:dell1800服务器,
- 很多时候,希望能够在 HTML 中使用空格排版。但浏览器在解析 HTML 时,会把连续的空格解析成一个,所以我们会使用
- 我们开发数据库应用时,常常需要用到模糊查询。如果同一个条件需要匹配很多字段怎么办呢?通常,程序员会每个字段都在SQL中“field like
- 超酷的js图片轮换/轮播 渐变效果··来自腾讯刚刚在腾讯女性频道上看到一个很酷的图片渐变轮换效果·····于是乎····抠下来了···分享·
- [本站原创]在我们浏览了一些网页时,经常会弹出一些信息窗口或浏览器窗口以显示一些公告内容,想知道这些窗口是怎么制作出来的吗?如果你还不曾知道