Redux入门教程
三大原则
-
单一数据源
整个应用的state被储存在一棵object tree中,并且这个object tree只存在于唯一一个store中。
每个应用只有一个store
-
State是只读的
唯一改变state 的方法就是触发action,action是一个用于描述已发生事件的普通对象。
-
使用纯函数来执行修改
为了描述action如何改变state tree,需要编写reducer
基础概念
Action
Action是把数据从应用传到store的有效载荷,它是store数据的唯一途径。
Reducer
Reducers指定了应用状态的变化如何响应actions并发到store。
Store
Store是把它们联系到一起的对象,它有以下职责:
- 维持应用的state;
- 提供
getState()
方法获取state; - 提供
dispath(action)
方法更新state; - 通过
subscribe(listener)
注册监听器;subscribe(listener)
方法返回一个函数,调用这个函数就可以解除监听。
工作流程
假设React组件上绑定了一个Action - Click事件:
- 当点击事件时,dispatch会分发对应的action
- action通过reducers对状态进行更新并返回到store当中
- store获得新状态之后又重新渲染到视图上
- 如此反复…
手把手实例
首先,我们通过create-react-app demo03
命令,创建一个React脚手架应用。
npm i redux --save
和 npm 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则通过Provider
和connect()
将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的进阶使用了。