1. redux
1.1 redux使用的场景
- 某个组件的状态需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态;
1.2 redux的设计思想
:::success web应用是一个状态机, 视图与状态是一一对应的
所有的状态, 保存在一个对象里面。 :::1.3 基本概念和 API
1.3.1 store
Store就是保存数据的地方, 可以把它看成一个容器,整个应用只能有一个Store。
Redux提供createStore这个函数, 用来生成store.
createStore函数接收另一个函数作为参数, 返回新生成的Store对象。import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools} from 'redux-devtools-extension'
//引入reduce
import reducer from './reducers'
//引入thunk 用来处理异步方法
import thunk from 'redux-thunk'
//暴露一个store
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));
1. store.getState()
const state = store.getState();
2. store.dispatch()
store.dispatch(addTodo('Learn Redux'));
3. store.subscribe()
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。 ```javascript import { createStore } from ‘redux’; const store = createStore(reducer);
store.subscribe(listener);
显然,只要把 **View **的**更新函数**(对于 React 项目,就是组件的render方法或setState方法)放入**listen**,就会实现 **View** 的**自动渲染**。<br />**store.subscribe**方法返回一个**函数**,调用这个函数就可以**解除监听**。
<a name="m2IWo"></a>
### 1.3.2 state
**Store**对象包含**所有数据**。 如果想得到某个数据, 就要对**Store**生成快照。这种时点的数据集合, 就叫做**State.**<br />当前时刻的**State,**可以通过**store.getState()**拿到。
```javascript
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
Redux规定, 一个state对应一个view。只要State相同,View就相同。你知道State,就知道View是什么样,反之亦然。
1.3.3 Action
state的变化, 会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。 Action就是View触发的通知, 表示State应该要发生变化了。
Action是一个对象。 其中type属性是必须的,是action的名称,描述当前发生的事情。data属性是可选的, 表示携带的参数。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
Action描述当前发生的事情。改变State的唯一办法,就是使用Action。它会运送数据到Store。
1. AcrionCreator:
View要发送多少种消息, 就会有多少种Action.如果都手写,会很麻烦。可以定义一个函数来生成Action,这个函数就叫做Action Creator。
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
上面代码中,addTodo函数就是一个 Action Creator。
- store.dispatch():
store.dispatch 是view触发Action的唯一方法。
//action creator
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
//store.dispatch
store.dispatch(addTodo('Learn Redux'));
1.3.4 Reducer
Store收到Actions以后, 必须给出一个新的state, 这样View才会发生变化。这种State的计算过程就叫做Reducer.
Reducer是一个函数, 它接收Action和当前State作为参数, 返回一个新的State.
import {INCREMENT,DECREMENT} from '../constants/count'
const initData = 0 //初始化状态
export default function increment(preState = initData, action){
//action对象中包含type和data
const {type, data} = action;
//根据type判断
switch (type) {
case INCREMENT: //+
return preState+data
case DECREMENT: //-
return preState-data
default:
return preState
}
}
实际应用中,Reducer函数不用像上面这样手动调用。store.dispatch方法会触发Reducer的自动执行。为此Store需要知道Reducer函数, 做法就是生成Store的时候,将Reducer传入creatorStore方法。
import { createStore } from 'redux';
const store = createStore(reducer);
上面代码中,createStore接收 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
1. combineReducers:
Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。
combineReducers方法用于Reducer的拆分。你只需要定义各个子Reducer函数, 然后用这个方法, 将他们合成一个大的Reducer.
import count from './count'
import person from './person'
import { combineReducers } from 'redux'
export default combineReducers({
count,
person
})
总之: combineReducer()做的就是产生一个整体的Reducer函数。该函数根据State的key去执行相应的子Reducer,并将返回结果合成一个大的State对象。
你可以把所有子 Reducer 放在一个文件里面,然后统一引入。
import { combineReducers } from 'redux'
import * as reducers from './reducers'
const reducer = combineReducers(reducers)
2. 纯函数:
Reducer是一个纯函数,也就是说,只要是同样的输入, 必定得到同样的输出。
纯函数是函数式编程的概念, 必须遵守以下的一些约束:
- 不得改写参数
- 不能调用系统I/O 的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.push(newItem) //这样是不允许的, 因为改变了原来的state.
}
最好把state对象设置为只读的。你没法改变他, 要得到新的State, 唯一的办法就是生成一个新的对象。 这样的好处就是,任何时候, 与某个View对应的State总是一个不变的对象。
4. applyMiddleware():
应用上基于redux的中间件(插件库)
//combineReducers的作用是把多个reducer合并成一个reduer
const allReducer = combineReducers({
he:countReducer,
persons:addPerson
})
//thunk的作用是允许store处理异步函数
export default createStore(allReducer, applyMiddleware(thunk));
1.4 工作流程
- 第一步: 用户发出Action
store.dispatch(action)
- 第二部:Store自动调用reducer,并且传入两个参数:当前state和收到的action。Reducer会返回一个新的State.
let nextState = todoApp(prevState, action);
- 第三步:State 一旦有变化,Store 就会调用监听函数。
store.subscribe(listener);
第四步: listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
2. react-redux
2.1 UI组件
UI组件有以下几个特征:
只负责UI的呈现, 不带有任何业务逻辑;
- 没有状态
- 所有数据都有参数提供的
不适用任何Redux的API
const Title =
value => <h1>{value}</h1>;
因为不含有状态,UI 组件又称为”纯组件“,即它纯函数一样,纯粹由参数决定它的值。
2.2 容器组件
容器组件的特征恰恰相反:
负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
2.3 connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
2.4 Provider组件
让所有组件都可以得到state数据
connect方法生成容器组件以后, 需要让容器组件拿到state对象, 才能生成UI组件的参数。
一种解决方法是将state对象作为参数, 传入容器组件。 但是这样比较麻烦, 尤其是容器组件可能在很深的层级, 一级级将state传下去很麻烦。
React-Redux提供了Provider组件, 可以让容器组件拿到state。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
一般情况下在跟组件外面包了一层, 这样依赖, APP的所有子组件就默认都可以拿到state了。它的原理是connect组件的context属性。
2.5 mapStateToProps()
mapStateToProps是一个函数。 他的作用是建立一个从(外部的)state对象到(UI组件的)Props对象的映射关系。
作为函数, mapStateToProps执行后返回一个对象, 里面的每一个键值对就是一个映射。
:::success
- mapStateToProps函数返回的是一个对象;
- 返回的对象中的key就作为传递给UI组件的props的key,value就作为传递给UI组件的props的value;
mapStateToProps用于传递状态; :::
const mapStateToProps = (state) => {
return {
fetchUpdate: state.fetchUpdate
}
}
mapStateToProps会订阅store,每当state更新的时候, 就会自动执行, 重新计算UI组件的参数, 从而触发UI组件的重新渲染。
mapStateToProps的第一个参数总是state对象, 但是它可以有第二个参数, 代表容器组件的props对象。const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibility
}
}
使用ownProps作为参数后, 如果容器组件的参数发生变化, 也会引发UI组件重新渲染。
connect方法可以省略mapStateToProps参数, 那样的话, UI组件就不会订阅Store, 也就是说Store的更新不会引起UI组件的更新。2.6 mapDispachToProps()
mapDispachToProps是connect函数的第二个参数, 用来建立UI组件的参数跟store.dispatch方法的映射。 也就是说, 它定义了哪些用户的操作应该当作Action, 传给store.它可以是一个函数, 也可以是一个对象。
如果mapDispachToProps是一个函数, 会得到dispach和ownProps(容器组件的props对象)两个参数。 :::successmapDispatchToProps函数返回的是一个对象
- 返回的对象中的key就是作为传递给UI组件props的key,value就作为传递给UI组件props的value
- mapDispatchToProps用于传递操作状态的方法
:::
如果mapDispachToProps是一个对象, 它的每个键名也是对应UI组件的同名参数, 键值应该是一个函数, 会被当作Action creator, 返回的Action 会由Redux自动发出。 ```javascript const mapDispatchToProps = (dispatch) => ({ fetchScheduleDetail: (query) => dispatch(fetchScheduleDetail(query)), })const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SchedulingOverview);
<a name="nALJ2"></a>
## 2.7 纯函数与高阶函数
<a name="FMhjc"></a>
### 2.7.1 纯函数
1. 一类特别的函数: **只要是同样的输入**(实参),**必定得到同样的输出**(返回)<br />2. 必须遵守以下一些约束
- **不得改写参数数据**
- **不会产生任何副作用**,例如网络请求,输入和输出设备
- **不能调用Date.now()或者Math.random()等不纯的方法 **
3. **redux**的**reducer**函数必须是**一个纯函数**
<a name="Wpowj"></a>
### 2.7.2 高阶函数
1. 理解: 一类特别的函数
- 情况1:** 参数是函数**
- 情况2: **返回是函数**
2. 常见的高阶函数:
- **定时器设置函数**
- **数组的forEach()/map()/filter()/reduce()/find()/bind()**
- **promise**
- react-redux中的**connect函数**
3. 作用: **能实现更加动态, 更加可扩展的功能**
```javascript
export default connect(
state =>({ count: state }),
{
jia:incrementAction,
jian:decrementAction,
jiaAsync:incrementActionAsync
}
)(CountUI)
6.3 redux-devtools-extension的使用
npm install redux-devtools-extension
2.8 代码优化
const mapStateToprops = (state) => {
console.log(state)
return {
sum: state
}
}
const mapDispatchToProps = dispatch => {
return {
increment: data => dispatch(incremnet(data)),
decrement : data => dispatch(decrement(data))
}
}
//使用connect创建并暴露一个CountContainer容器组件
export default connect(mapStateToprops,mapDispatchToProps)(CountContainer)
//第一种优化
const mapStateToprops = state => ({
sum: state
})
const mapDispatchToProps = dispatch => ({
increment: data => dispatch(incremnet(data)),
decrement : data => dispatch(decrement(data))
})
export default connect(mapStateToprops,mapDispatchToProps)(CountContainer)
//第二种优化
export default connect(
state => ({
sum: state
}),
dispatch => ({
increment: data => dispatch(incremnet(data)),
decrement : data => dispatch(decrement(data))
})
)(CountContainer)
//第三种优化 API层级得优化, 只传action, react-redux子根据你传得action自动做派发
export default connect(
state => ({
sum: state
}),
{
jia: incremnet,
jian: decrement
}
)(CountContainer)
3. redux与react-redux的区别:
3.1 redux
flux的基本原则时单项数据流。
redux的定位:它是将flux和函数式编程思想结合在一起形成的架构。
思想:视图与状态是一一对应的, 所有的状态都保存在一个store对象中。它是react中进行state状态管理的JS库, 并不是react插件,一般时管理多个组件中的共享数据状态。
redux的三个基本原则:
- 唯一数据源:唯一的数据源指的是应用的状态数据应该只存储在唯一的store上。这个store的状态是一个树形的对象, 每个组件往往只是树形对象上的一部分数据, 而如何设计Store上状态的结构, 就是Redux应用的核心问题。
- 保持状态只读:不能直接去修改状态, 要修改store的状态, 必须要通过派发一个action对象完成。改变状态的方法不是去修改状态上的值,而是创建一个新的状态对象返回给Redux,由Redxu完成新的状态的组装。
- 数据改变只能通过纯函数完成:这里说的纯函数就是Reducer.reducer(preState,action)。第一个参数是当前的状态,第二个参数action是接收到的action对象,而reducer函数要做的事情就是根据state和action的值产生一个新的对象返回。注意reducer必须是一个纯函数,也就是说函数的返回值必须由参数和state和action决定,而且不产生任何副作用,也不能修改state和action对象。
Redux API:
- store: 就是一个数据池,一个应用只有一个;
- state: 一个state对应一个View,只要State相同,View就相同。
- action:用来描述发生了什么。State的变化,会导致VIew的变化。但是用户接触不到state,只能接触到View。所以State的变化必须是VIew导致的。Action就是View发出的通知,表示State应用要发生变化了, Action是一个对象。 其中的type属性是必须的,表示Action名称。
- dispatch: 它是view触发action的唯一方法;
- reducer:view触发action后, state要发生变化,reducer就是改变state的处理曾, 它接收到action和state, 通过处理action来返回新的state;
- subscribe: 监听。监听state,state变化view随之改变。
基本流程:
3.2 react-redux
React-Redux: React-Redux是Redux的官方React绑定库。它能够使你的React组件从store中读取数据,并且向store分发actions以更新数据。React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
- UI组件:就像一个纯函数,没有state,不做数据处理,只关注view,长什么样子完全取决于接收了什么参数(props)容器组件:关注行为派发和数据梳理,把处理好的数据交给UI组件呈现;React-Redux规定,所有的UI组件都由用户提供,容器组件则是由React-Redux自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
- connect:这个方法可以从UI组件生成容器组件;但容器组件的定位是处理数据、响应行为,因此,需要对UI组件添加额外的东西,即mapStateToProps和mapDispatchToProps,也就是在组件外加了一层state;
- mapStateToProps:一个函数, 建立一个从(外部的)state对象到(UI组件的)props对象的映射关系。 它返回了一个拥有键值对的对象;
- mapDispatchToProps:用来建立UI组件的参数到store.dispatch方法的映射。 它定义了哪些用户的操作应该当作动作,它可以是一个函数,也可以是一个对象。
3.3使用区别:
- redux和组件进行对接的时候是直接在组件中进行创建。react-redux是运用Provider将组件和store对接,使在Provider里的所有组件都能共享store里的数据,还要使用connect将组件和react连接。
```javascript import React from ‘react’; import ReactDOM from ‘react-dom/client’; import ‘./index.css’; import App from ‘./App’; import reportWebVitals from ‘./reportWebVitals’; import store from ‘./redux/store’ import { Provider } from ‘react-redux’ const root = ReactDOM.createRoot(document.getElementById(‘root’)); root.render(//在组件中使用redux创建store
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState())
})
//容器组件 export default connect(mapStateToProps, mapDispatchToProps)(AutoPublishService); ```
- 获取state的方式不一样
redux获取state是直接通过store.getState()。
react-redux获取state是通过mapStateToProps函数,只要state数据变化就能获取最新数据。
- 触发action的方式不一样。
redux是使用dispatch直接触发,来操作store的数据。
react-redux是使用mapDispathToProps函数然后在调用dispatch进行触发。