一、Redux概念简述
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。简单来说,redux 就是一个储存数据的仓库,专注于状态管理,和 react 解耦。并且 redux 是单一状态,单向数据流的。
Redux要求把数据放到公共存储区域Store里,组件中尽量少放数据。当蓝色组件修改公共数据时,Store中的值发生改变,其他使用到该数据的组件到Store中取修改后的值即可。
Redux设计理念图
安装
$ yarn add redux
//或者也可以
$ cnpm install --save redux
附加包
多数情况下,可能还需要使用 React 绑定库和开发者工具。
$ cnpm install --save react-redux
$ cnpm install --save-dev redux-devtools
redux 中有4个核心概念,分别是: store
,state
,action
, reducer
store
指的是存储数据的仓库,我们的数据只有放在这里才可以被 redux 管理起来。Redux 提供了一个createStore
来生成Store。其中createStore
接收一个reducers
.state
指的是初始化的数据,这里的初始化数据是可以有多个的。action
指的是需要变化的数据,必须要有一个type参数和一些需要在state中修改的属性。reducer
因为store
在收到我们传递过去的action
之后需要对state
进行更新,这个计算过程就叫做reducer
。reducer是一个函数,接受state
和action
作为参数,返回一个新的state
.
二、Redux工作流程
ReactComponents
:每个组件(理解:借书的人)Store
:存储数据的公共区域(理解:图书管理员)Action Creators
:组件想获取的数据(理解:说的要借的书的话)Reducers
:Store查询的内容(理解:管理员查看书的记录本)
工作流程:可以看做是你到图书馆借书,你是借书的人[React Components],你说了一句你想借什么书[Action Creators],告诉图书管理员[Store],图书管理员查看他的记录本[Reducers],并返回给你你要的书。
用户在
view
层通过store.dispatch
来发送action
,当store
接收到用户传递过来的action
后,会把state
和action
传给reducer
,而reduce
r 会根据action
的type
,来返回一个新的state
。而当store
有变化的时候,store
就会调用监听函数store.subscribe
,从而使得view
层发生变化。
model
模型,用于保存状态;注意:不能直接修改model中的值。
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
action
如果想要修改 model 中的值,必须发起一个 action,这里需要注意的是 Action 实际上是一个普通的 js 对象。使用 action 来修改state的好处在于可以很清楚的描述发生了什么事情,例如 type 取值为 ‘ADD_TODO’ ,那我们就明白要添加 TODO,text 值为 TODO 的内容。
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
action 是数据从应用传递到 store 中的有效载荷。它是 store 数据的唯一来源!一般来说你会通过
store.dispatch()
将 action 传到 store。Action 本质上是一个对象,但是我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。除了 type 字段外,action 对象的结构完全由你自己决定。
let action = {
type:'TO_ADD' //action的名字
...
}
dispatch
传入数据,里面的参数是一个对象的形式,action 和要传入的值 比如
store.dispatch({ type:'GET_ALL', text:data })
, data 就是我们要存入store的数据 这样就是把数据存在仓库里面了,这个项目中的任意组件就都可以直接得到他的数据了。
store.dispatch(action);
getState
直接打印它得到的就是全部的数据,就是我们之前定义的一个对象的形式 如果想要得到某一条的数据,比如我们刚刚储存进去的 data 直接使用
store.getState().all
就可以得到刚刚储存的数据。
constructor(props) {
super(props);
this.state = store.getState();
}
subscribe
订阅者模式,store中的数据一更改,页面的订阅者发现,就去改页面的数据,让页面重新去渲染。
constructor(props) {
super(props);
this.state = store.getState();
store.subscribe(this.changeState); //订阅者做的事情
}
//store中的数据更改后的处理函数
changeState = () => {
...
}
三、Redux知识补充
Redux在设计和使用时有三个基本原则
store
是唯一的
所有组件均使用这一个store- 只有
store
能够改变自己的内容
reducer 拿到之前的数据并生成新的数据,将新的数据返回给 store,store 拿到新的数据并更新 Reducer
必须是纯函数
纯函数:给定固定的输入,就一定有固定的输出,而且不会有任何副作用(副作用指的是对参数的修改)。reducer 导出的就是纯函数,给定了具体的 state 和 action,一定有固定的输出内容,且在函数内部不会修改参数的值。
四、项目示例- - -备忘录
index.js
//[store/index.js]
//创建store仓库
import {createStore} from 'redux';
import reducer from './reducer';
//将reducer与store绑定,创建store并返回
export default createStore(reducer);
reducer.js
//[store/reducer.js]
import util from '../utils/util';
//函数 reducer
const initState = {
inputValue:'',
listData:[{
text:'备忘录1',
time:'2019-09-11 09:58:36',
status:'未完成'
},{
text:'备忘录2',
time:'2019-08-27 16:38:09',
status:'已完成'
}]
};
export default (state=initState,action)=>{
if (action.type === 'TO_CHANGE_INPUT'){
let obj = {
...state,
inputValue:action.value
};
// console.log(obj);
return obj;
}
if (action.type === 'TO_ADD') {
//修改仓库
let obj = {
text:state.inputValue,
time: util.parseData(),
status:'未完成'
};
let {listData} = state;
listData.push(obj);
return {
...state,
listData,
inputValue:''
}
}
if (action.type === 'TO_DELETE') {
let {listData} = state;
listData.splice(action.index,1);
return {
...state,
listData
}
}
if (action.type === 'TO_CHANGE_STATUS') {
let {listData} = state;
listData[action.index].status = '已完成';
return {
...state,
listData
}
}
return state;
}
util.js
//[utils/util.js]
//获取当前时间
function parseData(){
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth()+1;
let day = date.getDate();
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
return year+'-'+formatNum(month)+'-'+formatNum(day)+' '+formatNum(hours)+':'+formatNum(minutes)+':'+formatNum(seconds);
}
//时间格式化处理
function formatNum(num){
return num>10?num:('0'+num);
}
export default {
parseData
}
ToDo.js
//[components/ToDo.js]
import React, { Component } from 'react';
import {Button, Input, List} from 'antd';
import store from '../store';
class ToDo extends Component {
constructor(props) {
super(props);
this.state = store.getState();
store.subscribe(this.changeState); //订阅者做的事情
}
inputChange = (e)=>{
//获取input数据,更改store中的数据
let action = {
type:'TO_CHANGE_INPUT',
value:e.target.value
};
//分发action
store.dispatch(action);
// console.log(this.state);
}
changeState =()=>{
this.setState({
...store.getState()
});
}
toAdd = ()=>{
let action = {
type:'TO_ADD'
};
store.dispatch(action);
}
toDelete = (index)=>{
let action = {
type:'TO_DELETE',
index
};
store.dispatch(action);
}
toChangeStatus = (index)=>{
let action = {
type:'TO_CHANGE_STATUS',
index
};
store.dispatch(action);
}
render() {
return (
<div style={{padding:20}}>
<h2>备忘录</h2>
<div>
<Input type="text" value={this.state.inputValue} onChange={this.inputChange} style={{width:200}}/>
<Button type="primary" onClick={this.toAdd}>添加</Button>
</div>
<div style={{marginTop:10}}>
<List size="small"
bordered
dataSource={this.state.listData}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
title={<span style={{ color: item.status === '未完成' ? 'red' : 'green' }}>[{item.status}] {item.text} {item.time}</span>}
/>
<div>
{
item.status === '未完成' && <Button type="link" onClick={this.toChangeStatus.bind(this, index)}>完成</Button>
}
<Button type="link" onClick={this.toDelete.bind(this, index)}>删除</Button>
</div>
</List.Item>
)}
/>
</div>
</div>
);
}
}
export default ToDo;