Redux使用(纯Redux使用)

会简单介绍Redux,然后使用纯Redux (不是使用react-redux) 写一个例子

目前使用版本

"redux": "^4.0.5"

Redux

  • 1.单一数据源

Redux思想里面,一个应用永远只有一个数据源。

  • 2.状态只读

数据源里面的state,只能读。

  • 3.状态修改均由纯函数完成

在 Redux 里,我们通过定义 reducer 来确定状态的修改,而每一个 reducer 都是纯函数,这意味着它没有副作用,即接受一定的输入,必定会得到一定的输出。

流程图

redux流程

由图中可以看出,Store 是一个中心(数据源),通过Reducer创建Store的state,在由Store把state传递给Components(组件),Components(组件)在通过事件触发Action来进行变更 Store 里面state。

Redux中存在几个概念:state, action, dispatch

  • state: 组件的内部的状态
  • action: 组件动作,相应的改变组件内部的状态值
  • dispatch: 发出相应的动作

核心API

  • getState() : 获取State

获取 store 中当前的状态。

  • dispatch(action) : 执行Action,并返回这个Action

分发一个 action,并返回这个 action,这是唯一能改变 store 中数据的方式。

  • subscribe(listener):监听Store变化

注册一个监听者,它在 store 发生变化时被调用。

  • replaceReducer(nextReducer)

更新当前 store 里的 reducer,一般只会在开发模式中调用该方法。

  • createStore(reducers[, initialState]) : 创建Store

要想生成 store,必须要传入 reducers,同时也可以传入第二个可选参数初始化状态(initialState)

  • combineReducers(...)

是用来生成调用你的一系列 reducer , 每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理

reducers :是一个函数,用来变更Store的值。

函数样板:reducer(previousState, action) => newState
可以看出,reducer 在处理 action 的同时,还需要接受一个 previousState 参数。所以,reducer 的职责就是根据 previousState 和 action 计算出新的 newState。

function(state,action) {
    switch(action.type) {
       case: ADD_TYPE:
          // 处理具体的逻辑...

          return { ...state }
    }
}

完整Redux例子(TodoList):

ps: 这个例子不使用 react-redux, 而是直接使用 redux

创建一个react项目

npx create-react-app redux-dome

引入如下依赖

// 只用antd组件库
npm install antd --save
// 配置自定义
yarn add react-app-rewired customize-cra
// 组件按需加载
yarn add babel-plugin-import
// less 样式
yarn add less less-loader

项目根目录创建config-overrides.js用于修改默认配置, 并加入如下代码

const { override, fixBabelImports } = require('customize-cra');

module.exports = override(
    fixBabelImports('import', {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: 'css',
    }),
);

修改package.json 里面的启动配置,替换成我们的 react-app-rewired 的方式

// 将原来的 scripts 节点下启动方式替换如下
...
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test"
  },
...

编写出一个Todo页面,TodoList.js

// list/TodoList.js

import React, { Component } from 'react'
import { Input, Button, List } from 'antd';

/**
 * TodoList 一个列表
 */
class TodoList extends Component {
    constructor(props) {
        super(props)
        
        this.inpVal = React.createRef();

        this.state = {
             data: [
                 "这是第一行",
                 "这是第二行",
                 "这是第三行"
             ],
        }

    }

    addData() {
        const inputValue = this.inpVal.current.state.value
        console.log('当前值:', inputValue)
        if (inputValue === undefined) {
            return
        }
        this.setState({ data: this.state.data.concat(inputValue) })
        this.inpVal.current.state.value = undefined
    }

    render() {
        return (
            <>
                <div style={{ margin: '10px' }}>
                    <div>
                        <Input ref={ this.inpVal } placeholder="请输入内容" style={{ width: '200px' }}/>
                        <Button type="primary" style={{ marginLeft: '10px' }} onClick={this.addData.bind(this)}>确认</Button>
                        <List
                            style={{ marginTop: '10px', width: '200px' }}
                            bordered
                            dataSource={this.state.data}
                            renderItem={(item, index) => (
                                <List.Item key={index}>
                                    {item}
                                </List.Item>
                            )}
                        />
                    </div>
                </div>
            </>
        )
    }
}

export default TodoList

App.js文件修改

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './list/TodoList';

ReactDOM.render(<TodoList />, document.getElementById('root'));

运行起来

npm start

输入内容,然后点击确认,可以添加到 list 上


运行起来的界面

引入 Redux

npm install --save redux

创建 action.js 组件的动作

// store/action/action.js
// action.js 组件的动作

// 定义动作类型(action_type)
export const ADD_TYPE = "ADD_TYPE"

/**
 * 组件内的动作
 * 
 *    也就是我这个组件要发起一个什么样的动作
 *    比如我这里是发起的一个添加内容的操作
 * @param text 内容
 * @return 返回我这个动作的类型以及需要传递的内容
 */
function addText(text) {
    return {
        type: ADD_TYPE,
        text
    }
}

export { addText }

创建 reducer.js文件 发出相应的动作

// store/dispatch/reducer.js
// reducer.js 发出相应的动作

import { ADD_TYPE } from '../action/action'
import { combineReducers } from 'redux'

// 数据仓库
const defalutState = {
    data: [
        "这是第一行",
        "这是第二行",
        "这是第三行"
    ]
}


/**
 * 发出相应的动作
 *      这里的动作是直接针对 store 的操作
 * @param {*} state 
 * @param {*} action 
 */
function add(state = defalutState, action) {
    // 判断一下 action 是什么操作类型
    switch (action.type) {
        case ADD_TYPE:

            /**
             * 处理完一些逻辑后,需要把这个state返回出去,最好返回一个新的 state
             */
            console.log('action的add函数--text:', action.text)
            return {
                ...state,
                data: state.data.concat(action.text)
            }
        default:
    }

     // 不管何时,这个 state 都是返回的出去的
     return state
}

/**
 * combineReducers(...) 函数,是用来生成调用你的一系列 reducer
 * 每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理
 */
const apps = combineReducers({

    /**
     * 把你的定义的一些列相应动作都写入
     */

    add
})

export default apps

创建 store, 数据源

// store/index.js

import { createStore } from 'redux'
import reducers from './dispatch/reducer'

// 创建 store
const store = createStore(reducers)
export default store

修改 TotoList

import React, { Component } from 'react'
import { Input, Button, List } from 'antd';

// store 相关 start
import store from '../store/index'
import { addText } from '../store/action/action'
// store 相关 end

/**
 * TodoList 一个列表
 */
class TodoList extends Component {
    constructor(props) {
        super(props)

        // 打印所有的 store
        // console.log(store.getState())
        
        this.inpVal = React.createRef();

        this.state = {
            // data: [
            //     "这是第一行",
            //     "这是第二行",
            //     "这是第三行"
            // ],
            data: store.getState().add.data || []
        }

    }

    componentDidMount() {
        // 每次 state 更新时,打印日志
        // 注意 subscribe() 返回一个函数用来注销监听器
        this.unsubscribe = store.subscribe(() =>
            this.setState({ data: store.getState().add.data }),
            console.log('监听:', store.getState())
        )
    }

    componentWillUnmount(){
        // 停止监听
        this.unsubscribe()
    }

    addData() {
        const inputValue = this.inpVal.current.state.value
        console.log('当前值:', inputValue)
        if (inputValue === undefined) {
            return
        }
        // this.setState({ data: this.state.data.concat(inputValue) })
        store.dispatch(addText(inputValue))
        this.inpVal.current.state.value = undefined
    }

    render() {
        return (
            <>
                <div style={{ margin: '10px' }}>
                    <div>
                        <Input ref={ this.inpVal } placeholder="请输入内容" style={{ width: '200px' }}/>
                        <Button type="primary" style={{ marginLeft: '10px' }} onClick={this.addData.bind(this)}>确认</Button>
                        <List
                            style={{ marginTop: '10px', width: '200px' }}
                            bordered
                            dataSource={this.state.data}
                            renderItem={(item, index) => (
                                <List.Item key={index}>
                                    {item}
                                </List.Item>
                            )}
                        />
                    </div>
                </div>
            </>
        )
    }
}

export default TodoList

然后在运行


运行结果
浏览器控制台

关键点在于componentDidMount(){...}生命周期里面的监听

// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
this.unsubscribe = store.subscribe(() =>
      this.setState({ data: store.getState().add.data }),
      console.log('监听:', store.getState())
)

componentWillUnmount(){...}生命周期函数需要释放redux的state监听

// 停止监听
this.unsubscribe()

总结

通过上面的看出,使用纯 Redux 难度曲线比较高,而且繁琐!
通过store.subscribe() 进行监听,会带来性能消耗,而且会造成额外的UI界面的渲染!
所以不建议直接使用 Redux 库,而是使用 React-Redux 库来搭配 react 使用!

demo: https://github.com/weiximei/redux-demo

推荐阅读更多精彩内容