美文网首页
Redux使用(纯Redux使用)

Redux使用(纯Redux使用)

作者: 爱吃豆包 | 来源:发表于2020-03-09 15:06 被阅读0次

    会简单介绍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

    相关文章

      网友评论

          本文标题:Redux使用(纯Redux使用)

          本文链接:https://www.haomeiwen.com/subject/ddesdhtx.html