React学习笔记--在组件化开发中使用redux

在组件化开发中,不管是使用Vue还是React,都会遇到组件间数据传递的问题,两个框架都有各自的解决方法,一般采用的方法无非是

  1. 将数据放到全局供任何地方调用
  2. 通过组件间层层传递来共享数据
  3. 通过发布订阅的方式

一般情况下,如果只是父组件传到子组件,我们可以直接采用props来传递数据,不管在Vue中还是在React中都可以,但是一旦有多层组件嵌套,那么层层传递数据就会显得非常麻烦,而且后期也难以维护,为了解决这个问题,在React开发中,我们可以使用redux(在Vue中可以使用vuex:Vue学习笔记 在模块化开发中使用Vuex

Redux


简介

redux可以直接通过npm来安装

npm i redux --save

Redux和Vuex一样,都是状态管理器,Redux中通过将数据放到state仓库中,数据类型使用普通的对象来描述

{
	todoList:[{
		content:'todo 1',
		completed:false
	},{
		content:'todo 2',
		completed:true
	}],
	count:2
}

区别于一般对象的是,该对象没有setter,即不能直接修改这个对象,在Redux中,我们只允许通过发起action来修改state仓库,如果随意修改state,可能会造成难以修复的bug。通过发起action来修改Redux可以使数据流动更在清晰和可预测,且通过发起action来修改可以在redux-devtools中观察到,而直接修改state可能会无法触发props更新。

// action的发起举例
let action={
    type:"changeTitle",
    value:e.target.value
}
store.dispatch(action)

通过声明一个action,使用dispatch将其传递到reducer,reducer返回一个新的state供我们使用

Redux的三个原则

  1. 单一数据源
    整个React应用的数据被保存在一个对象树中,并且该树只存在唯一一个store中
{
	todoList:[{
		content:'todo 1',
		completed:false
	},{
		content:'todo 2',
		completed:true
	}],
	count:2
}
  1. state是只读的
    在Redux中,只能通过发起action来修改state,不能用其他方式来修改(这里的不能更多是一种规则/约束,实际上还是可以修改的,但是不建议这么做,会引起不必要的bug)
// action的发起举例
let action={
    type:"changeTitle",
    value:e.target.value
}
store.dispatch(action)
  1. 使用纯函数来执行修改
    Redux使用reducer来接收state和action,然后将新的state返回给store仓库。
function reducerFn(state=defaultState,action){
	if(action.type==='SHOW_DATA'){
		return action.value;
	}
	return state;	
}

纯函数:返回的值只受传入的参数的影响,不受其他因素的影响
如下面这个函数就不是纯函数

function showDate(){
	return new Date();
}

这个函数受现在的时间的因素影响,所以并非纯函数

核心

Action

前面已经说了,发起action是唯一可以修改state的方法,一个简单的action就只是一个普通的对象,在Redux中,约定action内必须有一个type属性,使用该属性来判断接下来要执行的动作,多数情况下,这个属性会被定义为字符串常量,当使用规模变大时,建议采用单独的模块或者文件来存放action,通过在action发起的文件/模块和reducer文件/模块中引入这些常量,可以让我们在字符串写错的时候有明确的报错。如果不使用这种方式,可能导致我们在发起action时因为type写错而静默失败

// actionTypes中声明常量
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
// action发起的文件和reducer文件引入常量
import { ADD_TODO, TOGGLE_TODO } from '../actionTypes'
通过函数来创建action

每当我们要修改state时,我们就需要发起action,但是每次都编写这些代码会显得冗余,而且也容易出错,一旦我们要修改常量名对应得那个变量,还要对每个都进行修改,通过使用函数来创建action就可以解决这些问题,使代码更容易维护

function createAddAction(val){
	return {
		type:ADD_TODO,
		val
	}
}

通过这种写法,使得我们可以更好地复用,在使用时,我们只要dispatch这个函数就可以了

store.dispatch(createAddAction('newVal'))
Reducer

Reducer是一个接收state和action的纯函数,通过action的type,来决定对state执行什么修改,而接收的state作为默认值,在一开始没有action时,就返回默认的state

function reducerFn(state=defaultState,action){
	if(action.type==='ADD_TODO'){
		// ...	
		return newState;
	}else if(action.type==='TOGGLE_TODO '){
		// ...
		return newState;
	}
	// ...
	return state;
}

根据官方文档中的要求,不要在reducer中执行下面的操作

  1. 修改传入的参数,即action和state
  2. 执行有副作用的操作,如API请求和路由跳转
  3. 调用非纯函数,像Date.now()或Math.random()

因为不能修改state,但是有时我们只是修改state中的一部分内容,希望能在原来的state的基础上进行修改再return回去,此时我们就要使用深拷贝
官方文档中使用Object.assign()

let newState=Object.assign({}, state, {
  visibilityFilter: action.filter
})

事实上,我们可以直接采用JSON方法来做到深拷贝

let newState=JSON.parse(JSON.stringify(state));
拆分Reducer

现在我们只是在做一个小的demo,那么当我们的应用变大时,由于state是单一的,我们不能像vuex里面拆分成多个文件,每个文件有自己的state
为了解决这个问题,我们可以拆分Reducer,每个拆分出来的Reducer去管理单一state中的不同部分的数据

function todos(state = [], action) {
  // ...
}

function visibilityFilter(state = SHOW_ALL, action) {
  // ...
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

上面的代码中,由todos和visibilityFilter分别管理state中的todos和visibilityFilter,通过这种方式,我们可以把管理不同部分的方法放到不同的文件中,再在总的reducer文件中引入,这样就可以达到拆分state的目的了
此外,在Redux中提供了combineReducers() 工具类来完成上面todoApp的工作

import { combineReducers } from 'redux'
const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp
// 上面的代码和下面的代码完全等价
export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

Store


Store实际上就是用来存储数据的,在一个应用中,Store有下列的职责

  1. 保存整个应用的数据
  2. 通过getState()来获取state中的数据
    store.getState()返回state对象树
  3. 通过dispatch(action)更新state
  4. 通过subscribe(listener)注册监听器
    这里可以使用下面的代码来在该组件中注册监听器
store.subscribe(()=>{
    this.setState(store.getState())
})
  1. 通过subscribe(listener)返回的函数注销监听器
创建store

创建store非常简单,使用createStore方法,传入reducer即可

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)

createState的第二个参数是可选的,用于设置state的初始状态,值得一提的是,如果要使用redux-devtools,那么就要在第二个参数中写上window.__REDUX_DEVTOOLS_EXTENSION__&&window.__REDUX_DEVTOOLS_EXTENSION__()

const store=createStore(
    todoApp ,
    window.__REDUX_DEVTOOLS_EXTENSION__&&window.__REDUX_DEVTOOLS_EXTENSION__()    
);

如果不写上这行,就会出现下面的情况
在这里插入图片描述

Redux中的数据生命周期


  1. 发起action(调用store.dispatch(action))
  2. Redux调用reducer中的函数
    通过store.dispatch,store将原来的state和action传给reducer,触发reducer中的函数
  3. 根reducer将多个子reducer的输出合并成一个单一的state树
    如果没有子reducer,那么就直接输出一个新的state树,如果有子reducer,那么首先要让各个子reducer输出各自管理的值,然后在根state树合并为一个单一的state树
  4. 调用store.subscribe(listener) 的监听器来更新UI
    在通过action发起修改state后,调用store.subscribe(listener)的监听器,让与修改数据相关的UI更新

具体在demo中如何使用,详见React入门 制作简单的todolist

发布了178 篇原创文章 · 获赞 12 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zemprogram/article/details/102258072