redux
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
- 代码结构
- 组件之间的通信
2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
需要使用redux的项目:
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要redux:
-
某个组件的状态,需要共享
-
某个状态需要在任何地方都可以拿到
-
一个组件需要改变全局状态
-
一个组件需要改变另一个组件的状态
redux的设计思想
-
Web 应用是一个状态机,视图与状态是一一对应的。
-
所有的状态,保存在一个对象里面(唯一数据源)。
redux的流程
1.store通过reducer创建了初始状态
2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
3.用户产生了操作,调用了actions 的方法
4.actions的方法被调用,创建了带有标示性信息的action
5.actions内部通过调用store.dispatch方法将标志性的action发送到了reducer中
6.reducer接收到action并根据标识信息判断之后返回了新的state
7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
reducer是纯函数
reducer是state最终格式的确定。它是一个纯函数,也就是说,只要传入参数相同,返回计算得到的下一个 state 就一定相同。
没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
reducer对传入的action进行判断,然后返回一个通过判断后的state,这就是reducer的全部职责
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。
纯函数是函数式编程的概念,必须遵守以下一些约束。
不得改写参数
不能调用异步的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
最好把 State 对象设成只读。你没法改变它,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变的对象。
redux有四个组成部分
store:用来存储数据
reducer:真正的来管理数据
actionCreators:创建action,交由reducer处理
view: 用来使用数据,在这里,一般用react组件来充当
![](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1587375274551&di=149da446fe0eda78e9d9bc87af2792e5&imgtype=0&src=http%3A%2F%2Fimg3.mukewang.com%2F5b7a7e8f0001161205000261.jpg)
1.创建store
从redux工具中取出createStore去生成一个store
2.创建一个reducer,然后将其传入到createStore中辅助store的创建
reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态
想要给store创建默认状态其实就是给reducer一个参数创建默认值
3.组件通过调用store.getState方法来使用store中的数据
4.组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer
5.reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变 reducer返回什么状态,store.getState就可以获取什么状态
6.我们可以在组件中,利用store.subscribe(callback)方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态
划分Reducer
在一个复杂的尤其是单页面的应用中,为了提高开发效率,我们都会采取协同开发的模式,因为系统的模块较多,各个大的功能板块都较为独立,而redux有一个思想:单一数据源,也就是store只能有一个。
所以,我们多会使用划分reducer的方法,将每个大板块所需维护的状态划分在不同的reducer中去管理,分别有自己的一套结构,再通过combineReducers合并在一起
需要注意的是,划分reducer之后,store会将数据也去根据划分之后的reducer来进行分开管理
// store/reducer.js文件
import {combineReducers} from "redux"
// 引入了分支的reducer文件
import todolist from "./todolist/reducer"
const reducer = combineReducers({
todolist //后续组件想要获取redux最新状态的话,需要store.getState().todolist.todos
})
export default reducer;
//TodoContent.js组件里面 获取数据的话需要按照拆分的reducer名称进行数据的模块获取
class TodoContent extends Component {
constructor(){
super()
this.state = {
todos:store.getState().todolist.todos
}
}
componentWillMount(){
//监听 状态的更改 订阅状态变化
//一旦状态改变了,这个函数就会执行
store.subscribe(()=>{
this.setState({
todos: store.getState().todolist.todos
})
})
}
.....此处省略代码
todos.map报错了? todos获取不到
}
react-redux
cnpm install react-redux -S
核心组件:
Provider 提供者 属性上通过store将数据派给容器组件
connect 用于连接容器组件与UI组件
connect() 返回一个函数,函数参数接收UI组件,返回容器组件
connect(mapStateToProps,mapDispatchToProps)(ui组件)
容器组件内部帮你做了 store.subscribe() 的方法订阅
状态变化 ==> 容器组件监听状态改变了 ==> 通过属性的方式传给UI组件 把
store.getState()
的状态转化为展示组件的props
使用
转化为展示组件props
上的属性
const mapStateToProps = state=>{
return state.todolist
}
转化为展示组件props
上的方法 (TodoInput.js)
const mapDispatchToProps = dispatch=>{
return {
addNewTodo:title=>{
let action = actionCreators.addNewTodo(title)
dispatch(action)
}
}
}
另外一种写法:
const mapDispatchToProps = actionCreators //actionCreators = {方法1,方法2}
redux中间件
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
做异步的操作在action里面去实现!需要安装redux中间件
redux-thunk redux-saga redux-promise
redux-thunk原理:
可以看出来redux-thunk最重要的思想,就是可以接受一个返回函数的action creator。如果这个action creator 返回的是一个函数,就执行它,如果不是,就按照原来的next(action)执行。
正因为这个action creator可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。换言之,redux的中间件都是对store.dispatch()的增强
dispatch一个action之后,到达reducer之前,进行一些额外的操作,就需要用到middleware。你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
高阶组件
高阶函数:一个函数内部返回一个新的函数,内部采用闭包的写法。
var add = x => {
return y => {
return x+y
}
}
高阶组件:(HOC) Higher-Order-Component
高阶组件本质上就是一个函数,内部可以接收一个组件,然后返回新的组件。
例如: React.memo() connect()
封装一个具有版权信息的高阶组件withCopy,内部接收一个组件,最终返回一个新的组件。
import React, { Component } from 'react'
//高阶组件,本质上是一个函数 withCopy(Comp) 返回一个新的组件
//高阶组件内部可以对传入进来的组件传递一些新的属性给他,那样的话Comp组件就可以通过this.props获取到外部传入来的属性了
//connect()(UI组件) ===> 大家就理解了UI组件的props上面就可以获取到redux状态与更改redux状态的方法了
const withCopy = Comp =>{
return class WithCopyRight extends Component{
render(){
return (
<div>
<Comp {...this.props}/>
©千锋教育
</div>
)
}
}
}
export default withCopy
About组件:
import React, { Component } from 'react'
//引入高阶组件withCopy
import withCopy from "./withCopy"
class About extends Component {
render() {
return (
<div>
About {this.props.info}
</div>
)
}
}
export default withCopy(About)
App组件:
<div>
<About info={"这是app给他传过去的"}/>
</div>
对于CRA的定制
因为我们发现,调用高阶组件的时候采用withCopy(About)代码,但是不够简洁优雅。
那么我们可以采用装饰器的写法实现调用高阶组件
@withCopy
class About extends Component {
render() {
return (
<div>
About {this.props.info}
</div>
)
}
}
export default About
很不幸,目前的cra是不支持这种写法,我们需要单独进行定制,让其支持这种写法。
-
yarn add react-app-rewired
-
对脚手架进行轻微的调整
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
}
- yarn start启动项目发现报错了,原因是因为根路径下面缺少 config-overrides.js文件
module.exports = function override(config, env) {
return config;
}
但是启动的时候,发现还是报错,原因是缺少对于decorators-legacy的支持。
-
如果想配置的更加方便的话,需要安装
customize-cra
, config-overrides文件yarn add customize-cra (前提需要react-app-rewired安装)
const {
addDecoratorsLegacy,
override
} = require("customize-cra");
module.exports = override(
addDecoratorsLegacy() //就代表让当前的cra支持decorators了!
)
其实就会发现 react面向社区化的。它把一些常用到的包都放在社区,用到的话单独去查找去进行相应的配置。
vue的话面向官方化,很多东西内部都帮助我们去实现了。 vue.config.js文件 标准