React学习(三):Redux入门教程

Redux入门教程

三大原则

  1. 单一数据源

    整个应用的state被储存在一棵object tree中,并且这个object tree只存在于唯一一个store中。

    每个应用只有一个store

  2. State是只读的

    唯一改变state 的方法就是触发action,action是一个用于描述已发生事件的普通对象。

  3. 使用纯函数来执行修改

    为了描述action如何改变state tree,需要编写reducer

基础概念

Action

Action是把数据从应用传到store的有效载荷,它是store数据的唯一途径。

Reducer

Reducers指定了应用状态的变化如何响应actions并发到store。

Store

Store是把它们联系到一起的对象,它有以下职责:

  • 维持应用的state;
  • 提供getState()方法获取state;
  • 提供dispath(action)方法更新state;
  • 通过subscribe(listener)注册监听器;subscribe(listener)方法返回一个函数,调用这个函数就可以解除监听。

工作流程

img

假设React组件上绑定了一个Action - Click事件:

  • 当点击事件时,dispatch会分发对应的action
  • action通过reducers对状态进行更新并返回到store当中
  • store获得新状态之后又重新渲染到视图上
  • 如此反复…

手把手实例

首先,我们通过create-react-app demo03命令,创建一个React脚手架应用。

npm i redux --savenpm i react-redux --save 分别安装Redux和React-Redux

PS:

我在刚开始接触Redux时,一直都没理清楚这两者的关系,实际上这是两个独立的存在。这里摘录网上的资料进行解释。

Redux:数据处理中心

React-Redux:连接组件和数据中心,也就是把React和Redux联系起来

npm i node-sass --save安装Sass,方便样式编写。

PS:

create-react-app 脚手架中已经添加了 sass-loader 的支持,所以只需要安装 node-sass 插件即可;

但如何是配置less则没有这么方便了,因为脚手架本身并没有配置关于less文件的解析。添加Less相关步骤如下:

  • 运行npm run eject命令,该命令会将脚手架中隐藏的配置都展示出来,此过程不可逆。

  • 之后,打开config目录下的webpack.config.js文件,找到// style files regexes注释位置,添加以下两行代码

    // 添加 less 解析规则
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\/module\.less$/;
    
  • 找到rules属性配置,添加less解析配置

    // Less 解析配置
    {
        test: lessRegex,
        exclude: lessModuleRegex,
        use: getStyleLoaders(
            {
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap,
            },
            'less-loader'
        ),
        sideEffects: true,
    },
    {
        test: lessModuleRegex,
        use: getStyleLoaders(
            {
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap,
                modules: true,
                getLocalIdent: getCSSModuleLocalIdent,
            },
            'less-loader'
        )
    }
    
  • 配置完成后,安装less和less-loader插件即可

    npm i less less-loader --save
    

此时,即可使用Less了

接着,我们将目录中多余的文件删除,src文件夹下仅留index.js文件即可。下面是我当前src文件夹下的目录结构:

- src
	- contaienrs // 用于使用react-redux,连接视图层和store
	- css // 存放各个视图层用到的css文件,通过index.scss文件进入导入
	- reducers // 用于存储reducers文件
		- actionCreator 用于存储action操作,便于管理
		- actionType 用于存储action type的变量,便于管理
	- views // 用于存储视图
	index.js
	index.scss // 用于导入所有scss文件

将目录结构整理好之后,就可以开始实例了,例子较为简单,主要是想通过例子学习和巩固React-redux的使用。


首先,创建store。在Redux当中,store是用来存储状态的,同时它也是唯一的,整个项目当中有且只有一个store。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import ToDoList from './containers/ToDoList';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducers/common';

const store = createStore(reducer)
ReactDOM.render(
    <Provider store={store}>
        <ToDoList />
    </Provider>,
    document.getElementById('root')
);

PS:

<Provider store>使组件层级中的connect()方法能够获得Redux store。这是React redux中的一个重要的组成部分。在还没有使用React redux之前,我们需要通过store.subscirbe对数据进行监控,但使用React redux则通过Providerconnect()将React和Redux联系起来。在我看来,它就像一座桥梁,更加的方便了我们使用Redux。

在刚开始接触的时候,一直不理解为什么单独使用Redux也可以,可是大部分人都会选择React redux呢?之后在实践的过程中才慢慢理解了这点。

此处写demo的时候还遇到一个坑,就是Provider包裹的地方,应与container的文件产生联系,因为container就是通过connetc将redeux和react联系起来。一开始我错误的将view包裹在里面,却发现似乎connect的文件一直没有生效。。。思考了很久之后才想明白了这件事。


接下来,我开始了编写了views文件夹里的ToDoList.js文件

// views ToDoList.js
import React, { useState, useEffect } from 'react';

function ToDoList(props) {
    const { inputValue, getInputVal, addItem, list } = props;
    const [toDoList, setToDoList] = useState([]);

    useEffect(() => {
        setToDoList(list);
    }, [list]);

    return (
        <div className="container">
            <h3>To Do List</h3>
            <div className="input">
                <input
                    type="textarea"
                    placeholder={inputValue}
                    onChange={getInputVal}
                    value={inputValue}
                />
                <button onClick={addItem}>添加</button>
            </div>
            <div className="list">
                <ul>
                    {toDoList &&
                        toDoList.map((item, index) => {
                            return <li key={index}>{item}</li>;
                        })}
                </ul>
            </div>
        </div>
    );
}

export default ToDoList;

这里是demo的所有代码,下面将会对里面的一些函数和变量进行讲解。

props在平常的父子组件当中,主要是用做传递父组件的数据。而在这里,则是和container容器中对应的文件联系起来。

// containers ToDoList.js
import { connect } from 'react-redux';
import ToDoList from '../views/ToDoList'
import { getInputAction, addItemAction } from '../reducers/actionCreator/toDoList'

const mapStateToProps = state => {
    console.log(state)
    return {
        inputValue: state.inputValue,
        list: state.list
    };
}

const mapDispatchToPros = dispatch => {
    return {
        getInputVal(e) {
            console.log(e.target.value);
            const action = getInputAction(e.target.value)
            dispatch(action)
        },
        addItem() {
            const action = addItemAction()
            dispatch(action)
        }
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToPros
)(ToDoList)

这个容器里面,就是通过connect将视图层和redux联系起来。

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

mapStateToProps(state, ownProps) : stateProps

这个函数允许我们将store中的数据作为props绑定到组件上, state则是可以获取store当中的变量。

mapDispatchToProps(dispatch, ownProps): dispatchProps

这个函数可以将action作为props绑定到组件上,而需要出发action则是通过dispatch进行分发。


最后,看看reducers里面的文件

// actionType toDoList.js
export const GET_INPUTVAL = 'getInputVal'
export const ADD_ITEM = 'addItem'
// actionCreator toDoList.js
import { GET_INPUTVAL, ADD_ITEM } from '../actionType/toDoList'

export const getInputAction = (value) => ({
    type: GET_INPUTVAL,
    value
})

export const addItemAction = () => ({
    type: ADD_ITEM
})
// reducers common.js
import { GET_INPUTVAL, ADD_ITEM } from './actionType/toDoList'

const defaultState = {
    inputValue: 'input something...',
    list: []
}

export default (state = defaultState, action) => {
    switch (action.type) {
        case GET_INPUTVAL:
            // let newState = JSON.parse(JSON.stringify(state))
            // newState.inputValue = action.value
            // console.log(newState)
            // return newState
            return Object.assign({}, state, {
                inputValue: action.value
            })
            break
        case ADD_ITEM:
            console.log('object')
            let newState = JSON.parse(JSON.stringify(state))
            newState.list.push(newState.inputValue)
            newState.inputValue = ''
            return newState
            break
        default:
            break;
    }
    return state
}

在Redux中,有一个重要的原则就是不能直接对state进行改变,state状态是不可变的,需要改变state则只能通过dispatch分发action,触发对应的reducer进行改变。

PS:

这里说个题外话,在编写reducer的时候,因为意识到state是不可变的,所以在返回新状态的时候需要对其进行深拷贝。也因此对深拷贝和浅拷贝产生了疑惑…(之后需要好好学学😢)


通过这个简单的实例,我对Redux和React-redux有了初步的认识,而在真正的项目当中,往往都不会只有一个reducer这么简单,因此学习如何合并多个reducer至关重要。在这里则需要学习combineReducers等其它API的进阶使用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章