前言
我们在学习react技术栈中,不可避免的会去接触学习redux,然而redux确是一个难点,它不同于之前学习原生js或者是react.js那样,直接写业务逻辑就能解决问题。而是更多的体现在一种架构思想上,什么时候用redux,为什么要用redux,以及如何去用redux。本文将用案例加说明的方式引导学习。
·需求基础
react.js+es6 你需要有这两个基础,如果没有,可以学习redux用法和思想,因为下文案例将采用react来开发。
1.什么是redux?
redux是一个状态管理库,它提供一种可控管理状态的方法,它能够帮你管理代码中的那些看起来很糟糕的状态,状态只是一些变量,那么这些变量自然有无数种方法去改变,你可以将这些变量看做你的孩子,你可以想象一下当你的孩子过多,几十个?上百个?而何况这些孩子还有可能朝着各种歪瓜裂枣的方向变化着。。。
redux正是基于这种情况,给状态限制在了一个框架中,任何不“正确的方式”将无法去修改,并且所有允许修改的方法,都可见且便于管理。
2.什么时候用redux?
如果你觉得不用redux很累,我想你会主动用的。
3.redux详解
在使用 Redux 之前,你必须要谨记它的三大原则:单一数据源、state 是只读的和使用纯函数执行修改。
3.1单一数据源
整个应用的 state 都被储存在一棵树中,并且这棵状态树只存在于唯一一个 store 中。
这使得来自服务端的 state 可以轻易地注入到客户端中;并且,由于是单一的 state 树,代码调试、以及“撤销/重做”这类功能的实现也变得轻而易举。
3.2只读的 state
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这就表示无论是用户操作或是请求数据都不能直接修改 state,相反它们只能通过触发 action 来变更当前应用状态。其次,action 就是普通对象,因此它们可以被日志打印、序列化、储存,以及用于调试或测试的后期回放。
3.3使用纯函数执行修改
函数在有相同的输入值时,产生相同的输出
函数中不包含任何会产生副作用的语句
在这里,reducer 要做到只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,只进行单纯执行计算。
3.4redux结构
请看我随手画的草图。
我们通过action发起修改state的请求,action通过自身的type属性找到reducer里对应的方法,reducer则经过一系列操作返回一个新的state对象,通过以旧换新将旧的state取代。(1)state(状态/数据)
state就是我们定义的状态。 我们只需要了解两点
1 .state存在于store中,并且state与store是一对一的关系,即单一的数据源state存在唯一的store中。
2 .state只能通过发起action去修改。
(2)action(修改state的唯一方法)
//添加
export function addtodo(todo) {
return{
type:"ADDTODO",
todo
}
}
这是一个简单的进行添加的action
1.action是一个对象,用于描述修改state的一些行为。
2.内部的第一个属性type,用于指定进行何种操作,对应的操作存在于reducer中,它的作用类似于指路牌。
2.第二个属性,todo是一个参数,它可以传入任何值,类似于形参,我们将会把它传入到reducer中去使用,用于动态去修改state。
(3)reducer (执行action的地方)
reducer里集成了我们将要对state所做的各种操作,这里不要将它于action混淆,action只是指引了对state的操作,而reducer则是操作的具体实现。
export default function active(state="all",action) {
switch (action.type){
case "UPDATEACTIVE":
return action.todo
default:
return state;
}
}
这是一个最简单的reducer结构,里面甚至只有一个action方法。reducer中有两个参数,除此之外,我们还需要了解reducer的一些特性。
1.第一个参数,代表着我们我们所定义的状态,在这里我们将state引入,并不是用于我们将要对state进行操作,reducer中的纯函数的特性也不允许我们去对state进行可能修改的操作。我们引入state是为了获取state的值,以便于我们组成新的值。
2.第二个参数,action,这就是我们传入的action,我们获取action.type决定执行何种方法,获取action.todo(todo的命名不固定)决定将如何去修改state。
3.永远不要去试图在reducer中修改state,这是不可取,也是不规范的。(如果直接修改,那要redux干嘛?) 所有直接修改state,或者可能对state造成影响的操作都被禁止,(我们所做的仅仅是克隆一个跟state长得比较像的数据进行返回取代state)
4.reducer并不一定是对整个state结构进行操作,如果state的结构树比较深,我们可以将reducer拆分,分别对部分state进行操作,最后只需要使用Redux 提供的 combineReducers() 工具类进行合并即可
import {combineReducers} from "redux"
import todos from "./todos"
import active from "./active"
const reducer = combineReducers({
todos,
active
})
export default reducer;
(4)store
Store 用来存放整个应用的 state,并将 action 和 reducer 联系起来。它主要有以下几个职能:
存储整个应用的 state,它提供了以下三个api接口
1. getState() 方法获取 state
2.dispatch(action) 方法更新 state
3.subscribe(listener) 来注册、取消监听器
import {createStore,applyMiddleware} from "redux"
import reducer from "../reducers/App"
const state = {
todos:[
{content:"redux学习",flag:false},
],
active:"all"
}
const store=createStore(reducer,state);
export default store;
4.具体案例(选看)
这是一个简单的案例,虽然简单,但内部具体逻辑功能有将近十几个,十分适合新人学习。
具体案例可以去todo官网查看---------> todo
我们新建一个基于react的项目 (使用 npm i create-react-app myapp 生成)
然后我们修改项目结构为如下。
具体的html,css代码,以及react的组件划分就不详解了,不是本文重点。
另外在react中使用redux需要安装这两个依赖库。redux和react-redux
1.编写state
const state = {
todos:[
{content:"redux学习",flag:false},
],
active:"all"
}
可以看到,我们的state对象中有两条属性,第一个todos就是我们的内容数据,其中有两个属性,第一个content是集体任务内容,第二个flag是定义任务的完成状态的,false为未完成。
第二个暂时不用(用来定义显示何种内容的)
2.编写action
src/action/todo.js
//添加任务
export function addtodo(todo) {
return{
type:"ADDTODO",
todo
}
}
//删除任务
export function deletetodo(todo) {
return{
type:"DELETETODO",
todo
}
}
//更新内容
export function updatecontent(todo) {
return{
type:"UPDATECONTENT",
todo
}
}
export function clear(todo) {
return{
type:"CLEAR",
todo
}
}
export function updateflag(todo) {
return{
type:"UPDATEFLAG",
todo
}
}
export function updateallflag(todo) {
return{
type:"UPDATEALLFLAG",
todo
}
}
export function updateactive(todo) {
return{
type:"UPDATEACTIVE",
todo
}
}
前三个我加了注释的action是本次案例讲解的三个方法,由于我事先已经将其余功能写完,为了不报错,就先不删除了,只需要关注前三个功能即可。
2.编写reducer
export default function todos(state=[],action) {
switch (action.type){
//添加。
case "ADDTODO":
return [...state,{content:action.todo,flag:false}]
//删除todo
case "DELETETODO":
var index=state.findIndex((item,index)=>index==action.todo)
var arr=[]
var j=0
for (var i=0;i<state.length;i++){
if (i!=index){
arr[j]=state[i]
j++
}
else {
}
}
return arr
//修改content
case "UPDATECONTENT":
var arr=[...state]
var index=state.findIndex((item,index)=>index==action.todo.keyid)
arr[index].content=action.todo.content
return arr
//统计未完成的任务(此任务不需要修改状态,所以不用写方法)
case "CLEAR":
var arr=[]
var j=0
for (var i=0;i<state.length;i++){
if (state[i].flag==false){
arr[j]=state[i]
j++
}
}
return arr
//单选更新状态
case "UPDATEFLAG":
var index=state.findIndex((item,index)=>index==action.todo)
var newflag=state[index].flag==true?false:true
var arr=[...state]
arr[index].flag=newflag
return arr
//全选更新
case "UPDATEALLFLAG":
var arr=[...state]
for (var i=0;i<arr.length;i++){
arr[i].flag=action.todo
}
return arr
default:
console.log('进错对象啦')
return state;
}
}
我将reducer进行了拆分这个代码是对todos内容的操作,todos是一个数组结构,所以我们只需要返回一个同样的数组结构即可。
2.1添加todos
我们已知在reducer中无法使用push等修改参数的方法。那我们如何在一个数字中添加数据呢?
这里我们就需要用到es6中的扩展运算符了 (在reducer中扩展运算符是老顾客)
既然[...state]=state 那么[...state,{content:action.todo,flag:false}]就相当于在state中添加了一条数据。我们直接返回即可。
我们来看看在addtodo组件中调用这个方法
add(e){
let content=e.target.value.trim();
if(content){
if(e.keyCode==13)
{
if(content){
this.props.add.addtodo(content)
e.target.value=""
}
}
}
}
可知当我们按下回车是,会将input中的内容作为参数调用此方法。当然,我这儿贴出的只是核心代码,集体组件的使用可以自己去操作。 我们来看看效果
好像还不错,你也可以继续添加,例如内容为空无法添加等,按esc放弃添加等。
2.1删除todos
删除基本逻辑是点击删除,调用删除的action,还要传入我们需要删除的内容,(注意传入的参数最好不要是具体的内容,因为同样的任务,数据很有可能是一样的,那么当我们删除,就有可能删错,最好传入要删除内容的数组下标)
deletetodo(){
setTimeout(()=>this.props.comments.deletetodo(this.props.keyid),1000)
}
deletetodo(){
this.props.comments.deletetodo(this.props.keyid)
}
这两个代码是没有区别的,使用定时器只是为了普及一个知识,我们在redux中如果不使用中间件Middleware来加强store的话,是无法使用异步操作的,但是我们在react中是可以使用异步操作的,我们在react中调用store中的同步操作,一样可以实现异步action操作。
还是直接来看看效果吧
2.1编辑todos
1.编辑功能第一个功能就是单机内容出现编辑框,并且编辑框内容为原来的内容,失去焦点编辑框消失,内容框显现。 2.修改功能,修改内容为失去焦点时编辑框的内容。
第一个功能既可以使用dom操作来完成,也可以使用新状态(此状态为todo组件私有),为了简单化,就采用dom来实现。 第二个功能则是调用更新内容的action
case "UPDATECONTENT":
var arr=[...state]
var index=state.findIndex((item,index)=>index==action.todo.keyid)
arr[index].content=action.todo.content
return arr
我们来回顾之前写的更新内容的reduce, 可以看到,我们传入的action.todo,必须包含两个参数,一个是要修改的数据下标,第二个就是新的内容。
我们直接上todo的失去焦点代码
onblur(e){
if (e.target.value.trim()==""){
this.props.comments.deletetodo(this.props.keyid)
}
else {
this.props.comments.updatecontent({ content:e.target.value.trim(),keyid:this.props.keyid})
}
var label=document.getElementsByClassName("label")
var edit=document.getElementsByClassName('edit')
for(var i=0;i<label.length;i++){
if (e.target==edit[i]){
label[i].style.display="block"
}
}
e.target.style.display="none"
}
写了那么多,只有这一句是核心代码,其余都是逻辑处理this.props.comments.updatecontent({ content:e.target.value.trim(),keyid:this.props.keyid})
老规矩,看看效果。
到这三个功能都讲解完了
总结
本文,在前面讲解了redux的基本用法,后面则以一个案例的形式应用了redux。
另外,案例虽然使用了react-redux,我在文中却并没有讲解react-redux的用法,因为redux只是一种架构思想,它可以用在任何地方,并不局限于react。如果你刚好学习react,那么你有必要去学习 redux和react-redux。如果是vue,那么你得去学vuex了。