Redux:
    Redux是一个数据管理框架,经常和React搭配适用。由于React写大型复杂应用遇到困难,2014年Fackbook提出Flux架构的概念。2015年,Redux出现,将Flux与函数式编程结合在一起。

    自带规范,将数据管理定义为三个核心概念,store, reducer,action。

    另一数据管理框架Mobx简单自由,但缺乏规范,两者分别适用于不同的场景。

    • 在学习Redux之前,先来弄清楚一个问题,也就是import 语句有时候带花括号{},有时候不带,有什么区别呢?如果适用不当,就会引起编译错误。

      例如:有个文件user.js,有两种情况。

    1. 不使用{}

      1. import user from './user';

      对于这种情况,只有user.js文件提供默认的export default导出,而且必须是默认导出,这样就可以在其他文件中使用user了。在这种不使用{}来引用模块的情况下,import模块时的命名是随意的,即如下两种写法都是正确的,因为它总是会被解析到user.js中默认导出 export default。

      1. import user2 from './user' (正确)
      2. import anyUser from './user' (正确)
    2. 使用花括号{}来import

      1. import {user} from './user';

      上面代码生效的前提是,只有当模块user.js中有如下命名导出为user的export语句,如下:

      1. export const user = 42;

      并且,在声明了命名导出后,name在另一个js中使用{}引用模块时,import时的模块命名是有意义的。并且只能写成如下形式。

      1. import {user} from './user'; (正确)
      2. import {anyUser} from './user'; (错误)

      一个js文件就是一个模块,一个模块只能有一个默认导出export default,但是可以有任意多的命名导出,你也可以一次性的将他们导入:

      1. import user, {user2, anyUser} from './user';

      上面代码使用默认导出的user,和命名导出user2,和anyUser。

    • 下面进入Redux学习

    先要引入redux包的支持,package.json中添加支持。

    1. "dependencies": {
    2. "react-redux": "~6.0.1",
    3. "redux": "~4.0.1"
    4. }

    从redux的核心概念的第一个概念入手,也就是action。action需要有type和data,type也就是一个字符串,起到标识的作用,store中的action的type必须唯一才能。暂时先忽略store文件,暂时先理解成store是一个数据仓库,后面会对store进行构建。

    1. import { store } from '../store';
    2. // 定义了一个action的type
    3. export const UPDATE_TITLE = 'UPDATE_TITLE';
    4. // 定义了一个 function, function就是向store派发一个action,
    5. // store会根据接收到的action去更新相应的数据
    6. export function updateTitle(data) {
    7. store.dispatch({
    8. type: UPDATE_TITLE,
    9. data: data
    10. });
    11. }

    有了action,下面来写reducer,reducer的中文叫汇总器(我自己的翻译哈),也就是用于来处理action,告诉store怎样去处理数据。root reducer有很多个子 reducer构成,也就是最终的一个大的reducer,当来了一个action的时候,action会到每个子的reducer中去匹配,看看有没有对这个action的处理,如果有,则更新相应数据,返回一个全新的store数据仓库里的数据(引用必须发生变化才可以,通常用Object.assign({}, state, data)来实现引用变化)。
    整个store构成的是一个树状结构的数据仓库。
    reducers/title.js

    1. import { UPDATE_TITLE } from '../actions/title';
    2. const initialState = "click me!";
    3. export default (state = initialState, action = {}) => {
    4. const { type, data } = action;
    5. switch(type) {
    6. case UPDATE_TITLE:
    7. return data;
    8. default:
    9. return state;
    10. }
    11. }

    上面构建了一个reducer,对应的数据只是一个字符串,初始值是”click me!”,当收到action后,并且匹配到action,就把数据更新为传进来的值。

    然后去把这些所有的reducer构建成一个root reducer。combineReducers就是把这些子的reducer 联合成一个reducer。

    这个文件起名为index.js,是有特殊用途的,后面使用时import rootReducer from ‘../reducers’; 并不需要写成
    import rootReducer from ‘../reducers/index’; 其实也就是一种简便的写法,个人认为老师讲解过程中并没有把这个说出来,新接触redux的同学会一头雾水。刚开始学的时候真的不建议各种简写,本来挺简单的东西,被这些奇怪的写法给绕进去了。如果这个文件起名不是index,比如叫root.js ,那么import的时候必须严格指定文件名。

    reducers/index.js

    1. import { combineReducers } from 'redux';
    2. import titleReducer from './title';
    3. const rootReducer = combineReducers({
    4. title: titleReducer
    5. // friend: friendReducer
    6. });
    7. export default rootReducer;

    上面的代码和老师讲的写法不一样,就是为了更清楚,我更喜欢这样的写法,不喜欢很骚的写法,虽然少写了几行,但是可读性降低了,对新手来说就更要命了。

    上面的代码中,我将title文件默认导出的reducer(一个function)命名为了titleReducer,来帮助理解消化。可以理解为:stroe里现在有一个节点,名字叫title,这个节点里对应的reducer是titleReducer,用来处理action引起的数据更新。当然store树里有可以有其他的节点,比如上面的friend节点。

    有了action,reducer,rootReducer之后,就可以构建store了。注意下面import rootReducer from ‘../reducers’;的时候,并没有指定具体文件,而是只指定了文件夹,他会去找index文件。
    下面的代码就是由指定的 rootReducer 去创建了一个store数据仓库。
    中间件middlewares中的redux-logger 是方便开发人员调试,当dispatch action的时候,会在控制台上输出日志,方便跟踪调试。

    1. import { compose, applyMiddleware, createStore } from 'redux';
    2. import rootReducer from '../reducers';
    3. const middlewares = [];
    4. if(process.env.NODE_ENV === 'development') {
    5. const { logger } = require('redux-logger');
    6. middlewares.push(logger);
    7. }
    8. export const store = compose(applyMiddleware(...middlewares))(createStore)(rootReducer);

    下面的代码 Provider 组件的属性名必须为store。到此为止,我们可以在App组件中肆意的玩耍store中的数据了。

    1. import React, { Component } from 'react';
    2. import ReactDOM from 'react-dom';
    3. import * as serviceWorker from './serviceWorker';
    4. import { Provider } from 'react-redux';
    5. import { store } from './store';
    6. import App from './App';
    7. ReactDOM.render(
    8. <Provider store={store}>
    9. <App/>
    10. </Provider>,
    11. document.getElementById('root'));

    App组件的代码:App组件里有两个子组件Friend和Home,一起来看一下,这两个组件是怎么访问或更新store中的数据的吧。

    1. import React from 'react';
    2. import './App.css';
    3. import Friend from './views/friend';
    4. import Home from './views/home';
    5. class App extends React.Component {
    6. render(){
    7. return (<div>
    8. <Friend/>
    9. <Home/>
    10. </div>);
    11. }
    12. }
    13. export default App;

    Home组件代码:

    在Home组件中,点击Button的时候调用function updateTitle,然后把要更新的数据传入,将store中相应的数据更新。export 导出的时,用connect方法,返回组件的props,connect方法的参数state是store中的数据,可以选择自己感兴趣的数据。

    需要注意的是:connect方法构造了Home组件的props,在Home组件中点击button调用updateTitle来dispatch派发一个action,reducer匹配到这个action,就会更新store中相应的数据。store中数据被更新了,Home组件中的props的值也会被更新,这就是为什么title发生变化的原因。

    1. import React, {Component} from 'react';
    2. import { connect } from 'react-redux';
    3. import { updateTitle } from '../../actions/title';
    4. class Home extends Component {
    5. constructor(props){
    6. super(props);
    7. this.state = {
    8. value: ''
    9. };
    10. }
    11. render() {
    12. return (<div>
    13. <input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}/>
    14. <button onClick={() => {
    15. if(this.state.value) {
    16. updateTitle(this.state.value);
    17. }
    18. }}>Change</button>
    19. <h2>{this.props.title}</h2>
    20. </div>);
    21. }
    22. }
    23. export default connect((state) => {
    24. return {
    25. title: state.title
    26. };
    27. })(Home);

    Friend组件代码:

    原理和Home组件一样。

    1. import React from 'react';
    2. import { addFriend, delFriend } from '../../actions/friend';
    3. import { connect } from 'react-redux';
    4. class Friend extends React.Component {
    5. constructor(props) {
    6. super(props);
    7. this.state = {
    8. value: 'a'
    9. };
    10. }
    11. render() {
    12. const { friendList } = this.props;
    13. return (<div>
    14. <input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}/>
    15. <button onClick={(e) => addFriend(this.state.value)}>Add</button>
    16. {
    17. friendList.map((item) => <p key={item.id}>{item.name}<button onClick={(e) => delFriend(item.id)}>Delete</button></p>)
    18. }
    19. </div>);
    20. }
    21. }
    22. export default connect((state) => {
    23. return {
    24. friendList: state.friend.list
    25. };
    26. })(Friend);
    • UI效果

    image.png

    当点击button Add和Delete的时候,可以看到console里有日志输出,这其实就是middlewares 提供的功能。
    日志包含之前store的值,派发的action,和当前store的值。

    点Add的日志:
    image.png

    点Delete的日志:
    image.png

    点Change的日志:**

    image.png

    • 函数组件中使用Redux

      先来看代码:
      它和上面的例子最明显的区别就是没有全局的store,Redux会帮我们定义全局store。
      使用useReducer 函数,传入参加reducer和initialState, 返回一个state,也就是store的数据,还有dispatch方法,有了这个state就可以访问里面的数据,想更新数据的话可以用dispatch来派发一个action。 ```javascript import React, {useReducer} from ‘react’; import {render} from ‘react-dom’;

    const initialState = { count: 1};

    function reducer(state, action) { switch(action.type){ case ‘reset’: return initialState; case ‘increment’: return { count: state.count+1}; case ‘decrement’: return { count: state.count-1}; default: return state; } }

    function Counter() { const [state, dispatch] = useReducer(reducer, initialState);

    1. return (<div>
    2. Count: {state.count}
    3. <button onClick={() => dispatch({type: 'reset'})}>
    4. Reset
    5. </button>
    6. <button onClick={() => dispatch({type: 'increment'})}>
    7. Add
    8. </button>
    9. <button onClick={() => dispatch({type: 'decrement'})}>
    10. Minus
    11. </button>
    12. </div>);

    }

    render(, document.getElementById(‘root’)); ```