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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容