Introduction to redux
This article assumes that everyone has some knowledge about react and flux architecture, and has also used or understood redux, so it will not start from the most basic, but directly redux summarizes. If you have not used redux, it is best to read here first
To understand redux, we must first summarize some design principles of redux:
Single data source
Redux only uses a single object tree structure to store the state of the entire application, that is, the data that will be used in the entire application, called store (storage). In addition to the stored data, the store can also store the status of the entire application (including the router status, which will be introduced later). Therefore, through the store, it becomes possible to implement an instant saving function (create a snapshot) of the entire application. In addition, this design It also provides the possibility for server-side rendering.
The status is read-only
This is in line with the design concept of flux. We cannot change the status of the store in components (actually Redux will generate a store based on the reducer), but can only trigger actions to iterate the current state through dispatch. Here we do not directly modify the application state, but return a brand new state.
State modifications are all composed of pure functions
The prototype of the reducer in Redux will look like the following, you can think of it as It is the formula of previous state + action = new state:
(previousState, action) => newState
Each reducer is a pure function, which means it has no side effects. The benefit of this design is not only that the reducer is used to modify the state. It is simple and can be tested purely. In addition, redux can save each return status to easily generate time travel and track the results of each change caused by the action.
If we use redux in react, we need both react-redux and redux.
This part mainly talks about my own understanding. It may be a bit abstract or not completely correct, so you can skip it directly.
The core method in redux is createStore. The core functions of react are all covered in createStore and its final generated store. The createStore method itself supports passing in three parameters: reducer, initialState, and enhancer. , enhancer can be used as an enhanced packaging function, which is not very commonly used by us.
This function maintains a currentState internally, and this currentState can be returned through the getState function (built-in). In addition, it actually implements a publish-subscribe mode and subscribes to events through store.subscribe. This work is performed by react-redux helps us do it implicitly, which is to trigger all listeners and update the entire state tree when there is a dispatch. In addition, the built-in dispatch function triggers the reducer after a series of verifications, and then the state is changed, and then the listener is called in sequence to complete the update of the entire state tree.
Friends who have used redux are actually familiar with middleware such as redux-thunk. In fact, it is indispensable in many cases. Redux also has good support for middleWare. , I think this concept is somewhat similar to the middleware mechanism of nodejs: the action passes through each middleWare in turn and then is passed to the next one. Each middleWare can also perform other operations such as interrupting or changing the action until the final processing function is handed over to the reducer.
The applyMiddleware function of redux is very refined:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) //注意这里的dispatch并不是一开始的store.dispatch,实际上是变化了的 } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
The core is dispatch = compose(...chain)(store.dispatch). This sentence is a chain call for each middleware. The source code of compose:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
Calls the execution result of the previous function to the next function.
In fact, the process of writing a middleware is very simple. For example, redux-trunk actually consists of this:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
Of course, we first It is stated that the react-router of the react tool set does not necessarily have to be used with redux, but redux has another react-router-redux that can be used with react-router and redux, and the effect is very good.
Because we do not introduce how to use react-router in this part, please refer to the Chinese document for the usage of react-router.
Allow developers to declare routes through JSX tags. This makes our routes very friendly to write and has the ability to express declarative routes. Relatively strong.
Nested routes and route matching: You can pass parameters in the specified path:
<Route path="/mail/:mailId" component={Mail} />
In addition, if the parameters are optional, we Just wrap it in parentheses (:optional parameter).
Supports multiple route switching methods: We know that the current route switching methods are nothing more than using hashchange and pushState. The former has better browser compatibility, but it is not like a The real URL, while the latter provides us with an elegant URL experience, but it requires the server to solve the problem of refreshing any path (the server must automatically redirect to the home page).
简单的说,react-router-redux让我们可以把路由也当作状态的一部分,并且可以使用redux的方式改变路由:直接调用dispatch:this.props.push(“/detail/”);,这样把路由也当作一个全局状态,路由状态也是应用状态的一部分,这样可能更有利于前端状态管理。
react-router-redux是需要配合react-router来使用的,并不能单独使用,在原本的项目中添加上react-router-redux也不复杂:
import { createStore, combineReducers, compose, applyMiddleware } from 'redux'; import { routerReducer, routerMiddleware } from 'react-router-redux'; import { hashHistory } from 'react-router'; import ThunkMiddleware from 'redux-thunk'; import rootReducer from './reducers'; import DevTools from './DevTools'; const finalCreateStore = compose( applyMiddleware(ThunkMiddleware,routerMiddleware(hashHistory)), DevTools.instrument() )(createStore); console.log("rootReducer",rootReducer); const reducer = combineReducers({ rootReducer, routing: routerReducer, }); export default function configureStore(initialState) { const store = finalCreateStore(reducer, initialState); return store; }
另外,上文提到的demoreact-router-redux-demo用了react-router和react-router-redux,当然也用到了redux的一些别的比较好的工作,比如redux-devtools,有兴趣的朋友可以点击这里
这一部分讲述的是一种组件书写规范,并不是一些库或者架构,这些规范有利于我们在复杂的项目中组织页面,不至于混乱。
从布局的角度看,redux强调了三种不同的布局组件,Layouts,Views,Components:
Layouts: 指的是页面布局组件,描述了页面的基本结构,可以是无状态函数,一般就直接设置在最外层router的component参数中,并不承担和redux直接交互的功能。比如我项目中的Layouts组件:
const Frame = (props) => <p className="frame"> <p className="header"> <Nav /> </p> <p className="container"> {props.children} </p> </p>;
Views组件,我认为这个组件是Components的高阶组件或者Components group,这一层是可以和redux进行交互并且处理数据的,我们可以将一个整体性功能的组件组放在一个Views下面(注:由于我给出的demo十分简单,因此Views层和Components层分的不是那么开)
Components组件,这是末级渲染组件,一般来说,这一层级的组件的数据通过props传入,不直接和redux单向数据流产生交互,可以是木偶般的无状态组件,或者是包含自身少量交互和状态的组件,这一层级的组件可以被大量复用。
总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。
redux的单向数据流相对于双向数据绑定,在处理表单等问题上的确有点力不从心,但是幸运的是已经开源了有几个比较不错的插件:
redux-form-utils,好吧,这个插件的star数目非常少,但是他比较简单,源代码也比较短,只有200多行,所以这是一个值得我们看源码学习的插件(它的源码结构也非常简单,就是先定一个一个高阶组件,这个高阶组件可以给我们自己定义的表单组件传入新的props,定制组件,后一部分就是定义了一些action和reducer,负责在内容变化的时候通知改变状态树),但是缺憾就是这个插件没有对表单验证做工作,所以如果我们需要表单验证,还是需要自己做一些工作的。
另外还有一地方,这个插件源代码写法中用到了::这种ES6的语法,这其实是一种在es6中class内部,使用babel-preset-stage-0即可使用的语法糖:::this.[functionName] 等价于 this.[functionName].bind(this, args?)
redux-form,这个插件功能复杂,代码完善,体量也非常庞大,可以参考文档进行使用,但是读懂源代码就是比较麻烦的事情了。不过这个插件需要在redux的应用的state下挂载一个节点,这个节点是不需要开发者自己来操控的,他唯一需要做的事情就是写一个submit函数即可。我在自己的demo中也把一个例子稍加改动搬了过来,感觉用起来比较舒服。
想要做到redux性能优化,我们首先就要知道redux的性能可能会在哪些地方受到影响,否则没有目标是没有办法性能优化的。
因为我也不是使用redux的老手,所以也并不能覆盖所有性能优化的点,我总结两点:
有的时候,我们需要的数据格式会自带冗余,可以抽取出一些公共的部分从而缩减大小,比如我们需要的数据格式可能是这样的:
[ { name:"Nike", title:"国家一级运动员","国家一级裁判员" } { name:"Jenny", title:"国家一级裁判员" } { name:"Mark", title:"国家一级运动员" } ]
这个时候实际上我们可以优化成这样:
[ { "国家一级运动员":"Nike","Mark" "国家一级裁判员":"Jenny","Nike" } ]
这个时候,我们可以直接把后者当作store的格式,而我们用reselect这个库再转变成我们所要的格式,关于reselect怎么用上述链接有更详细的例子,在这里我就不过多介绍了。
事实上,对于redux来说,每当store发生改变的时候,所有的connect都会重新计算,在一个大型应用中,浪费的时间可想而知,为了减少性能浪费,我们可以对connect中的selector做缓存。
The reselect library mentioned above has its own caching feature. We can determine whether to use caching by comparing parameters. The pure function feature is used here.
reselect’s cache function can be customized by the user
For more in-depth articles related to the redux technology stack, please pay attention to the PHP Chinese website!