Redux学习手册—详述基本概念及基本使用等原理(附详细案例代码解析过程)

  • Post author:
  • Post category:其他




1. 重点提炼

  • redux

    • 管理应用数据(它提供了一套管理的模式)
    • 纯函数(便于测试和重构)
  • 规范

    • store:仓库
    • state:数据
    • reducer:数据操作
    • action:动作

      • dispatch:派发



2. Redux

注意

Redux



React

是一点关系都没有的,它其实是js当中的一个状态(数据)管理的库。



2.1 知识点

  • 状态管理器
  • state 对象
  • reducer 纯函数
  • store 对象
  • action 对象
  • combineReducers 方法
  • react-redux
  • provider 组件
  • connect 方法
  • Redux DevTools extension 工具
  • 中间件 – middleware
  • redux-chunk



2.2 状态(数据)管理

前端应用越来越复杂,要管理和维护的数据也越来越多,为了有效的管理和维护应用中的各种数据,我们必须有一种强大而有效的数据管理机制,也称为状态管理机制,

Redux

就是解决该问题的。状态管理即有一定的规则,

Redux

的基本功能是做数据管理的,但是仅仅把数据存起来再取,就没啥意义了,重点在于

数据



管理

,得有一定地规则和规范,才能称之为

管理



2.3 Redux


Redux

是一个独立的

JavaScript

状态管理库,与非

React

内容之一

https://www.bootcdn.cn/redux/

官网:https://www.redux.org.cn/



2.3.1 核心概念

理解

Redux

核心几个概念与它们之间的关系

  • state:

    存储数据的位置,对象
  • reducer:

    数据分片(对数据分片、构建基础数据的函数)
  • store:

    仓库,提供数据存储、管理等操作的对象,state、reducer、action 都是 store 其中一个功能
  • action:

    提交数据操作请求

    (这里是不能直接修改state数据的,必须先发送请求才行,如果希望数据被有效管理,则不是任何人可以去修改数据的。就如同React中props传递进来,在组件内部是千万不能修改的,而是由控制数据的这个人去修改,即假如数据存在state中,想要对数据进行操作,必须提前发送请求。发了请求之后,由reducer来决定这个数据,它是构建基础数据的函数,它来维护数据的一些基础的函数。实际上state是由reducer产生的数据,它产生后的数据就会保存在state中,如果想修改数据,则通过action操作。)
  • state、reducer、action均放在store对象中



2.3.1.1

state

对象

通常我们会把应用中的数据存储到一个对象树(

Object Tree

) 中进行统一管理,我们把这个对象树称为:

state


(与React中state是一个东西,同样React中的state也得通过setState去修改,它就类似redux中reducer了)



2.3.1.1.1

state

是只读的

这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改

state

中的原数据



2.3.1.1.2 通过纯函数修改

state

什么是纯函数?实际就是

reducer



2.3.1.1.2.1 纯函数


特点

  1. 相同的输入永远返回相同的输出
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用(不做与自己功能无关的事)


使用纯函数的好处

  1. 便于测试
  2. 有利重构


2.3.1.1.3 example01

纯函数可理解为目的很单纯的函数,它不会对传入的参数进行修改,举个例子:

如这就不符合纯函数了,因为其对参数进行了修改。

这样其实不利于测试,其实这样修改参数,实际就代表

user1

的原数据就没了,比如过来很多天,你想看过去这个变量修改前的值,就没法看了。

redux-demo\1.redux-纯函数.html

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        user.age++;
    }
 
    growUp(user1);

所以我们不能修改参数,把函数改为:返回新对象

相同的输入有相同的输出(伪代码:合并对象)

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        //user.age++;
 
        return {
                ...user,
                {age: user.age+1}
        }
 
    }
 
    growUp(user1);

或者:

Object.assign()

方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        // user.age++;
 
        // return {
        //         ...user,
        //         {age: user.age+1}
        // }
 
        return Object.assign({}, user, {age: user.age + 1});
    }
 
    let user2 = growUp(user1);
    let user3 = growUp(user2);
 
    console.log(user1);
    console.log(user2);
    console.log(user3);

image-20200608162258161

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.01

Branch:branch1

commit description:a0.01-example01(redux-纯函数)

tag:a0.01



2.3.1.2 Reducer 函数

function todo(state, action) {
  switch(action.type) {
    case 'ADD':
      return [...state, action.payload];
      break;
    case 'REMOVE':
      return state.filter(v=>v!==action.payload);
      break;
    default:
      return state;
    }
}

上面的

todo

函数就是

Reducer

函数

  1. 第一个参数是原

    state

    对象

  2. Reducer

    函数不能修改原

    state

    ,而应该返回一个新的

    state
  3. 第二参数是一个

    action

    对象,包含要执行的操作和数据
  4. 如果没有操作匹配,则返回原

    state

    对象



2.3.1.3 action 对象

我们对

state

的修改是通过

reducer

纯函数来进行的,同时通过传入的

action

来执行具体的操作,

action

是一个对象


  • type

    属性 : 表示要进行操作的动作类型,增删改查……

  • payload

    属性 : 操作

    state

    的同时传入的数据

但是这里需要注意的是,我们不直接去调用

Reducer

函数,而是通过

Store

对象提供的

dispatch

方法来调用



2.3.1.4 Store 对象

为了对

state



Reducer



action

进行统一管理和维护,我们需要创建一个

Store

对象



2.3.1.4.1 Redux.createStore 方法
let store = Redux.createStore((state, action) => {
  // ...
}, []);


todo

用户操作数据的

reducer

函数


[]

初始化的

state

我们也可以使用

es6

的函数参数默认值来对

state

进行初始化

let store = Redux.createStore( (state = [], action) => {
  // ...
} )


2.3.1.4.2 getState() 方法

通过

getState

方法,可以获取

Store

中的

state

store.getState();


2.3.1.4.3 example02


Store

对象——创建仓库、获取

state



2.3.1.4.3.1 example02-1

创建仓库方法:构建一个用来存储数据,提供数据操作的仓库


Redux.createStore

: 创建仓库的函数

  • 第一个参数:

    reducer

    函数,修改数据的方法
  • 第二个参数:仓库中存储的初始化数据,存什么?

redux-demo\2.redux基础.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script src="./redux.min.js"></script>
<script>
 
 
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
    let store = Redux.createStore( function () {
 
    }, users)
 
    console.log(store);
</script>
</body>
</html>

操作数据的方法:

image-20200608170851308

获取仓库数据:

第一个参数是函数,它的作用是初始化,定义数据如何变化的。

    let store = Redux.createStore( function () {
 
    }, users)
 
    console.log(store);
    console.log( store.getState() );

返回的是

undefined

image-20200608171013063


第一个参数



reducer

函数,修改数据的方法,

如何操作,操作的结果?访问数据的时候,返回什么,数据变化如何进行?

创建仓库的第一个参数,即该函数其实有两个参数,该函数的返回值就是仓库中

state

的最新的数据。(刚才我们没有返回所有是

undefined

该方法会在仓库创建的时候默认调用一次。

第一个参数函数同时它会把第二个参数作为参数,传递给第一个参数函数,这个参数函数执行完毕的返回值就是该仓库最新的数据。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.02-1

Branch:branch1

commit description:a0.02-1-example02-1(redux基础——获取仓库数据,但state为undefined)

tag:a0.02-1



2.3.1.4.3.2 example02-2

构建一个用来存储数据,提供数据操作的仓库


Redux.createStore

: 创建仓库的函数

  • 第一个参数:

    reducer

    函数,修改数据的方法,如何操作,操作的结果?访问数据的时候,返回什么,数据变化如何进行?该函数的返回值就是仓库中

    state

    的最新的数据,该方法会在仓库创建的时候默认调用一次。
  • 第二个参数:仓库中存储的初始化数据,存什么?
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);
        return state;
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );


{type: "@@redux/INIT4.s.h.2.t.b"}

是创建仓库(

Redux.createStore

)首次调用传进来的,是默认传的

createStore

=>

reducer(users, {type: “@@redux/INITg.w.0.g.1.a”})

=>

return state

=>

现在仓库里面的数据。

image-20200608172119794

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.02-2

Branch:branch1

commit description:a0.02-2-example02-2(redux基础——获取仓库数据及原理)

tag:a0.02-2



2.3.1.4.4 dispatch() 方法

通过

dispatch

方法,可以提交更改

store.dispatch({
  type: 'ADD',
  payload: 'zs'
})


2.3.1.4.5

action

创建函数


action

是一个对象,用来在

dispatch

的时候传递动作和数据,我们在实际业务中可能会中许多不同的地方进行同样的操作,这个时候,我们可以创建一个函数用来生成(返回)

action

function add(payload) {
  return {
    type: 'ADD',
    payload
  }
}
 
store.dispatch(add('zs'));
store.dispatch(add('ls'));
...


2.3.1.4.6 subscribe() 方法

可以通过

subscribe

方法注册监听器(类似事件),每次

dispatch


action

的时候都会执行监听函数,该方法返回一个函数,通过该函数可以取消当前监听器。刚刚代码中,我们每次调用

dispatch

方法,就

console

一下,很麻烦,可以写进监听器中进行优化。

let unsubscribe = store.subscribe(function() {
  console.log(store.getState());
});
unsubscribe();


2.3.1.4.7 example03


dispatch

方法:当我们调用

dispatch

的时候其实就是执行的

reducer

函数

createStore

=>

store.dispatch({type: “@@redux/INITg.w.0.g.1.a”}}) 默认情况下



2.3.1.4.7.1 example03-1
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
 
    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);
        return state;
    }
 
    let store = Redux.createStore( reducer, users );
 
    console.log(store);
    console.log( store.getState() );
 
    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });


dispatch

:当我们调用

dispatch

的时候其实就是执行的

reducer

函数

createStore

=>(等同于)

store.dispatch({type: “@@redux/INITg.w.0.g.1.a”}})

image-20200608173917373

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-1

Branch:branch1

commit description:a0.03-1-example03-1(redux基础——dispatch)

tag:a0.03-1



2.3.1.4.7.2 example03-2


reducer

函数通过

action

参数来分辨,现在想做什么事情?我们根据

action

的值来做一些判断,做相应的操作。经常根据

action.type

做一些对应的操作。

<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                // 没做任何操作,就返回仓库上一次的状态
                return state;
        }
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );

    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });

    console.log( store.getState() );

image-20200608174750526

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-2

Branch:branch1

commit description:a0.03-2-example03-2(redux基础——reduce函数使用action参数)

tag:a0.03-2



2.3.1.4.7.3 example03-3

千万不要如下返回,因为我们要保持仓库的数据与上次的数据完全隔离的。(保持纯函数特性)

<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);

        switch (action.type) {
            case 'ADD':
                // 千万不要如下返回,因为我们要保持仓库的数据与上次的数据完全隔离的
                state.push(action.payload);
                return state;
            // return [...state, action.payload];
            default:
                // 没做任何操作,就返回仓库上一次的状态
                return state;
        }
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );

    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });

    console.log( store.getState() );

导致两次数据一样了,就追踪不到上次的数据了。

image-20200608175032032

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-3

Branch:branch1

commit description:a0.03-3-example03-3(redux基础——reduce函数使用action参数注意的问题)

tag:a0.03-3



2.3.1.4.7.4 example03-4

但每次都这样调用

store.dispatch

函数,会很繁琐。

\redux-demo\3.action函数.html

我们可以把这个过程封装成函数(其实就是

action

的工厂函数),这个函数返回一个

action

。这样方便我们后续代码的复用,否则每次追加数据,都需填写

“type”、“payload”

,很麻烦,代码也冗余。如果后续修改的添加(

添加用户

),如将

“type”

改为

“ADD_USER”

等,你得到处去改

“type”

,其实这样代码的耦合度非常高,独立性很差,维护起来很困难。其实这样也非常符合纯函数的特性。

    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );

    console.log(store);

    console.log( store.getState() );


    function add(payload) {
        return {
            type: 'ADD',
            payload
        }
    }


    store.dispatch(add({
        id: 3,
        username: 'xm',
        age: 30
    }));

    console.log( store.getState() );

    store.dispatch(add({
        id: 3,
        username: 'xh',
        age: 30
    }));

    console.log( store.getState() );

image-20200608180047977

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-4

Branch:branch1

commit description:a0.03-4-example03-4(action工厂函数封装)

tag:a0.03-4



2.3.1.4.7.5 example03-5

以上我们每次调用

store.dispatch

后再

console

,这样很麻烦,我们可以给

store

添加一个监听器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="./redux.min.js"></script>
<script>



    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );

    let unsubscribe = store.subscribe(function() {
        console.log(store.getState());
    });

    // console.log(store);

    // console.log( store.getState() );


    function add(payload) {
        return {
            type: 'ADD',
            payload
        }
    }


    store.dispatch(add({
        id: 3,
        username: 'xm',
        age: 30
    }));

    // console.log( store.getState() );

    store.dispatch(add({
        id: 3,
        username: 'xh',
        age: 30
    }));

    // console.log( store.getState() );


</script>
</body>
</html>

image-20200707135507181

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-5

Branch:branch1

commit description:a0.03-5-example03-5(加监听器)

tag:a0.03-5



2.3.2 Redux 工作流


UI

界面(组件,如

Vue、React

等)需要数据的时候,就可以从仓库的

State

中拿数据了,然后展示在

UI

界面上,当界面上更改数据的时候,不能直接修改

State

,这里是单向数据流。当需要修改

State

时,先提交

Actions

动作,然后匹配

Reducers

对应的行为(函数)之后再执行,

Reducers

对应函数的返回值将作为新的

State

。其实

React

组件的设计的也是这种单向数据流,如子组件想用父组件数据,从

props

取数据,但是子组件想修改父组件的数据,它需要调用父组件传递的回调函数才行,也是单向数据流。其实目的就是让数据的修改可控,可追踪到数据的变化。

img



2.3.3 Reducers 分拆与融合

当一个应用比较复杂的时候,状态数据也会比较多,如果所有状态都是通过一个

Reducer

来进行修改的话,那么这个

Reducer

就会变得特别复杂。这个时候,我们就会对这个

Reducer

进行必要的拆分

let datas = {
  user: {},
  items: []
  cart: []
}

我们把上面的

users



items



cart

进行分拆

// user reducer
function user(state = {}, action) {
  // ...
}
// items reducer
function items(state = [], action) {
  // ...
}
// cart reducer
function cart(state = [], action) {
  // ...
}



2.3.3.1

combineReducers

方法

该方法的作用是可以把多个

reducer

函数合并成一个

reducer

let reducers = Redux.combineReducers({
  user,
  items,
  cart
});
 
let store = createStore(reducers);



2.3.3.2 example04


Reducers

分拆与融合



2.3.3.2.1 example04-1

如下:这些数据会随着应用的复杂,数据越来越庞大,假设我们的数据非常庞大,封装

reducer

    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 
 
    function reducer(state, action) {
 
        switch (action.type) {
            case 'ADD_USER':
                return {
                    items: state.items,
                    cart: state.cart,
                    users: [...state.users, action.payload]
                };
            default:
                return state;
        }
 
    }
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-1

Branch:branch1

commit description:a0.04-1-example04-1(复杂数据封装reducer)

tag:a0.04-1



2.3.3.2.2 example04-2

但是如果

user

里还嵌套其他的内容,这样的话层级会非常得多,

reducer

返回值设置起来就会非常恶心。我们可以

reducer

拆开,每个

reducer

负责自己的模式。

 
    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 
 
    // state默认是users空数组
    function usersReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_USER':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function itemsReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_ITEM':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function cartReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_CART':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    // 拆分后再融合
 
    function reducer(state, action) {
        return {
            users: usersReducer(state.users, action),
            items: itemsReducer(state.items, action),
            cart: cartReducer(state.cart, action)
        }
    }
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-2

Branch:branch1

commit description:a0.04-2-example04-2(复杂数据封装reducer——分拆)

tag:a0.04-2



2.3.3.2.3 example04-3

鉴于这种需求比较强烈,

redux

提供了一个方法:

combineReducers

=>

就是完成了我们上面封装的

myReducer

的功能

    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 

    // state默认是users空数组
    function usersReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_USER':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function itemsReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_ITEM':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function cartReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_CART':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    // 拆分后再融合
 
    // function myReducer(state, action) {
    //     return {
    //         users: usersReducer(state.users, action),
    //         items: itemsReducer(state.items, action),
    //         cart: cartReducer(state.cart, action)
    //     }
    // }
  
    let reducer = Redux.combineReducers({
        users: usersReducer,
        items: itemsReducer,
        cart: cartReducer
    });
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-3

Branch:branch1

commit description:a0.04-3-example04-3(复杂数据封装reducer—Redux.combineReducers)

tag:a0.04-3



2.3.4 小结

以上我们学的

redux

是原生

js

写的,比较单一,真正在

react

中直接用

redux

,还需要做很多工作。不能直接用,如果直接用会出现很多问题。如数据变化以后,组件更新的问题以及调用等其他问题。

因此

react

还有一个第三方库,叫

react-redux

,创建仓库都类似,重点是提供

Provider

组件,通过这个组件把创建的仓库注入到应用当中,然后就可以在组件当中调用数据,也不是直接就可以调,而是通过

connect

方法,它类似

withRouter

,通过

connect

把组件进行包装。之后组件内部就直接可以调用

getState

方法及

dispatch

方法了,因为默认情况组件的

props

下是没有

dispatch

方法了,所有通过

connect

方法向组件

props

中注入dispatch方法,然后用

this.props.dispatch

可直接调用就行了。类似非路由组件通过

withRouter

进行包装,它底下就会

location、history

等这些对象了。

重点和难点在于

异步 action

,这里涉及到一个

Middleware



redux

的中间件。主要解决异步数据更新的问题。


(后续待补充)



版权声明:本文为u013946061原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。