1. 外围概念
1. 什么是 Redux?
Redux 是一个治理状态(数据)的容器,提供了可预测的状态治理
2. 什么是可预测的状态治理?
数据 在什么时候,因为什么,产生了什么扭转,都是能够管制和追踪的,咱们就称之为预测的状态治理
3. 为什么要应用 Redux?
React 是通过数据驱动界面更新的,React 负责更新界面,而咱们负责管理数据
默认状况下咱们能够在每个组件中治理本人的状态,然而当初前端应用程序曾经变得越来越简单
状态之间可能存在依赖关系(父子、共享等),一个状态的变动会引起另一个状态的变动
所以当应用程序简单的时候,状态在什么时候扭转,因为什么扭转,产生了什么扭转,就会变得十分难以管制和追踪
所以当应用程序简单的时候,咱们想很好的治理、保护、追踪、管制状态时,咱们就须要应用 Redux
4.Redux 核心理念
通过 store 来 保留数据
通过 action 来 批改数据
通过 reducer 来 关联 store 和 action
2. 三大准则
1.Redux 三大准则
繁多数据源
整个应用程序的 state 只存储在一个 store 中
Redux 并没有强制让咱们不能创立多个 Store,然而那样做并不利于数据的保护
繁多的数据源能够让整个应用程序的 state 变得不便保护、追踪、批改
State 是只读的
惟一批改 State 的办法肯定是触发 action,不要试图在其余中央通过任何的形式来批改 State
这样就确保了 View 或网络申请都不能间接批改 state,它们只能通过 action 来形容本人想要如何批改 stat;
这样能够保障所有的批改都被集中化解决,并且依照严格的程序来执行,所以不须要放心 race condition(竟态)的问题;
应用纯函数来执行批改
通过 reducer 将 旧 state 和 action 分割在一起,并且返回一个新的 State:
随着应用程序的复杂度减少,咱们能够将 reducer 拆分成多个小的 reducers,别离操作不同 state tree 的一部分
然而所有的 reducer 都应该是纯函数,不能产生任何的副作用
2. 什么是纯函数
返回后果只依赖于它的参数,并且在执行过程外面没有副作用
// 纯函数
function sum(num1, num2){return num1 + num2;}
// 非纯函数
let num1 = 10;
function sum(num2){return num1 + num2;}
// 纯函数
const num1 = 10;
function sum(num2){return num1 + num2;}
3. 根本应用
筹备工作
创立 demo 目录
cd demo
npm init -y #初始化一个 node 我的项目
npm install –save redux #装置 redux
redux 的应用
store.subscribe() #监听函数(一旦 state 发生变化,就主动执行这个函数)
store.getState() #获取以后的 state
store.dispatch() #派发 action(用于批改 state)
// 导入 redux
const redux = require('redux');
// 1. 定义一个状态(数据)let initialState = {count: 0}
// 2. 利用 store 保留状态
const store = redux.createStore(reducer);
// 3. 利用 action 批改状态
const addAction = {type:'ADD_COUNT', num: 1};
const subAction = {type: 'SUB_COUNT', num: -1};
// 4. 利用 reduce 关联 store 与 action
function reducer(state = initialState, action){switch(action.type){
case 'ADD_COUNT':
return {count: state.count + action.num};
case 'SUB_COUNT':
return {count: state.count + action.num};
default:
return state;
}
}
// 在组件中如何应用?// 1. 监听状态的扭转
store.subscribe(()=>{console.log(store.getState());
}
)
// 2. 获取 Store 中存储的状态
console.log(store.getState());
// 3. 批改 Store 中存储的状态
store.dispatch(subAction);
// console.log(store.getState());
以后代码存在的问题
1.store、action、reducer 代码都写在一个文件中,不利于保护
2.action 和 reducer 中都是应用字符串,来指定和判断操作类型,写错不报错
3.action 中的操作写死了,不够灵便
优化
const redux = require('redux');
// 定义常量
const ADD_COUNT = 'ADD_COUNT';
const SUB_COUNT = 'SUB_COUNT';
// 定义一个状态
let initialState = {count: 0};
// 利用 store 来保留状态(state)const store = redux.createStore(reducer);
// 利用 action 来批改状态
// const addAction = {type:ADD_COUNT, num: 1};
// const subAction = {type:SUB_COUNT, num: -1};
const addAction = (num)=>{return {type:ADD_COUNT, num: num};
};
const subAction = (num)=>{return {type:SUB_COUNT, num: num};
};
// 利用 reducer 将 store 和 action 串联起来
function reducer(state = initialState, action) {switch (action.type) {
case ADD_COUNT:
return {count: state.count + action.num};
case SUB_COUNT:
return {count: state.count - action.num};
default:
return state;
}
}
// 在组件中如何监听状态的扭转?store.subscribe((a)=>{console.log(store.getState());
});
// 在组件中如何从 Store 中获取存储的状态?console.log(store.getState());
// 在组件中如何批改 Store 中存储的状态?// store.dispatch(addAction(5));
store.dispatch(subAction(5));
// console.log(store.getState());
4. 联合 React
- npm install –save redux
-
在 src 目录下,新建 store 目录
store/constants.js
// 定义常量
export const ADD_COUNT = 'ADD_COUNT';
export const SUB_COUNT = 'SUB_COUNT';
store/store.js
import {createStore} from 'redux';
import reducer from './reducer';
// 利用 store 来保留状态(state)const store = createStore(reducer);
export default store;
store/action.js
import {
ADD_COUNT,
SUB_COUNT
} from './constants';
// 利用 action 来批改状态
// const addAction = {type:ADD_COUNT, num: 1};
// const subAction = {type:SUB_COUNT, num: -1};
export const addAction = (num)=>{return {type:ADD_COUNT, num: num};
};
export const subAction = (num)=>{return {type:SUB_COUNT, num: num};
};
store/reducer.js
import {
ADD_COUNT,
SUB_COUNT
} from './constants';
// 定义一个状态
let initialState = {count: 666};
// 利用 reducer 将 store 和 action 串联起来
function reducer(state = initialState, action) {switch (action.type) {
case ADD_COUNT:
return {count: state.count + action.num};
case SUB_COUNT:
return {count: state.count - action.num};
default:
return state;
}
}
export default reducer;
App.js
import React from 'react';
import store from './store/store';
import {addAction, subAction} from './store/action';
class App extends React.PureComponent{constructor(props){super(props);
this.state = {// 1.getState():获取存储的状态(数据)count: store.getState().count}
}
// componentDidMount:当组件被挂载时(曾经实现渲染),react 会主动调用该办法
componentDidMount() {// 3.subscribe():监听状态的扭转
store.subscribe(()=>{
// 将批改后的数据从新存储到 state 中
this.setState({count: store.getState().count
})
})
}
// componentWillUnmount:当组件被卸载时,react 会主动调用该办法
componentWillUnmount() {// 4.unsubscribe():移除监听状态的扭转事件
store.unsubscribe();}
render(){
return(
<div>
<p>{this.state.count}</p>
<button onClick={()=>{this.btnClick()}}> 减少 </button>
</div>
)
}
btnClick(){ // 2.dispatch():批改 Store 中存储的状态
store.dispatch(addAction(5));
}
}
export default App;
存在问题:
1. 冗余代码太多, 每次应用都须要在构造函数中获取
2. 每次应用都须要监听和勾销监听
3. 操作 store 的代码过于扩散
如何优化:
如何解决冗余代码太多问题
应用 React-Redux
什么是 React-Redux
React-Redux 是 Redux 官网的绑定库, 可能让咱们在组件中更好的读取和操作 Redux 保留的状态
装置 react-reduct:npm install react-reduct
index.js
import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';
import {Provider} from 'react-redux'
import store from './store/store';
ReactDOM.render(
// 只有利用 Provider 将先人组件包裹起来
// 并且通过 Provider 的 store 属性将 Redux 的 store 传递给 Provider
// 那么就能够在所有后辈中间接应用 Redux 了
<Provider store={store}>
<React.StrictMode>
<App/>
</React.StrictMode>
</Provider>
, document.getElementById('root'));
components/Home.js
import React from 'react';
import {connect} from 'react-redux';
import {addAction, subAction} from '../store/action';
class Home extends React.PureComponent{render(){
return (
<div>
{/* 3. 通过 props 来应用 redux 中保留的数据 */}
<p>{this.props.count}</p>
<button onClick={()=>{this.props.increment()}}> 递增 </button>
</div>
)
}
}
// 1.mapStateToProps 办法:通知 React-Redux, 须要将 store 中保留的哪些数据映射到以后组件的 props 上
const mapStateToProps = (state)=>{
return{count: state.count}
}
// 2.mapDispatchToProps 办法:通知 React-Redux, 须要将哪些派发的工作映射到以后组件的 props 上
const mapDispatchToProps = (dispatch)=>{
return{increment(){dispatch(addAction(1));
}
}
}
// 4.connect:关联 Home 组件与 mapStateToProps 和 mapDispatchToProps 办法
export default connect(mapStateToProps, mapDispatchToProps)(Home);
5.Redux 解决网络申请
前提
1. 搭建一个 Egg 我的项目
2. 疾速启动 Egg 我的项目(npm run dev)
3. 输出:localhost:7001/info 即可拜访页面
store/constants.js
// 定义常量
export const ADD_COUNT = 'ADD_COUNT';
export const SUB_COUNT = 'SUB_COUNT';
export const CHANGE_INFO = 'CHANGE_INFO';
store/store.js
import {createStore} from 'redux';
import reducer from './reducer';
// 利用 store 来保留状态(state)const store = createStore(reducer);
export default store;
store/action.js
import {
ADD_COUNT,
SUB_COUNT,
CHANGE_INFO
} from './constants';
// 利用 action 来批改状态
// const addAction = {type:ADD_COUNT, num: 1};
// const subAction = {type:SUB_COUNT, num: -1};
export const addAction = (num)=>{return {type:ADD_COUNT, num: num};
};
export const subAction = (num)=>{return {type:SUB_COUNT, num: num};
};
export const changeAction = (info)=>{return {type:CHANGE_INFO, info: info};
};
store/reducer.js
import {
ADD_COUNT,
SUB_COUNT,
CHANGE_INFO
} from './constants';
// 定义一个状态
let initialState = {
count: 666,
info: {}};
// 利用 reducer 将 store 和 action 串联起来
function reducer(state = initialState, action) {switch (action.type) {
case ADD_COUNT:
return {...state, count: state.count + action.num};
case SUB_COUNT:
return {...state, count: state.count - action.num};
case CHANGE_INFO:
return {...state, info: action.info};
default:
return state;
}
}
export default reducer;
components/About.js
import React from 'react';
import {connect} from 'react-redux';
import {addAction, changeAction} from '../store/action';
class About extends React.PureComponent {
// componentDidMount:曾经挂载(渲染实现)时,react 会主动调用该办法
componentDidMount() {
// 发送 GET 申请
fetch('http://localhost:7001/info')
.then((response) => {
// 转换为 json 格局
return response.json();})
.then((data) => {// console.log(data);
this.props.changeInfo(data);
})
.catch((error) => {console.log(error);
})
}
render() {
return (
<div>
{/* 3. 通过 props 来应用 redux 中保留的数据 */}
<p>{this.props.count}</p>
<button onClick={() => { this.props.increment() }}> 递增 </button>
<p>{this.props.info.name}</p>
<p>{this.props.info.age}</p>
</div>
)
}
}
// 1.mapStateToProps 办法:通知 React-Redux, 须要将 store 中保留的哪些数据映射到以后组件的 props 上
const mapStateToProps = (state) => {
return {
count: state.count,
info: state.info
}
}
// 2.mapDispatchToProps 办法:通知 React-Redux, 须要将哪些派发的工作映射到以后组件的 props 上
const mapDispatchToProps = (dispatch) => {
return {increment() {dispatch(addAction(1));
},
changeInfo(info) {dispatch(changeAction(info));
}
}
}
// 4.connect:关联 Home 组件与 mapStateToProps 和 mapDispatchToProps 办法
export default connect(mapStateToProps, mapDispatchToProps)(About);
6.Redux-thunk 中间件
优化:将 异步申请 封装到 Redux 中
1. 以后保留异步数据存在的问题
异步数据既然要保留到 Redux 中, 所以获取异步数据也应该是 Redux 的一部分
所以获取异步数据的代码应该放到 Redux 中, 而不是放到组件生命周期办法中
2. 如何在 Redux 中 获取网络数据
应用 redux-thunk 中间件
3.redux-thunk 作用
默认状况下 dispatch 只能接管一个对象
应用 redux-thunk 能够让 dispatch 除了能够接管一个对象以外, 还能够接管一个函数
通过 dispatch 派发一个函数的时候可能去执行这个函数, 而不是执行 reducer 函数
4.redux-thunk 如何应用
装置 redux-thunk
npm install redux-thunk
在创立 store 时利用 redux-thunk 中间件
在 action 中获取网络数据
在组件中派发 action
store/store.js
import {createStore, applyMiddleware} from 'redux';
import reducer from './reducer';
// 1. 导入 thunkMiddleware 中间件
import thunkMiddleware from 'redux-thunk';
// 2. 利用 thunkMiddleware 中间件
// 在创立 store 之前, 通过 applyMiddleware 办法, 通知 Redux 须要利用哪些中间件
const storeEnhancer = applyMiddleware(thunkMiddleware);
// 利用 store 来保留状态(state)const store = createStore(reducer, storeEnhancer);
export default store;
store/action.js
import {
ADD_COUNT,
SUB_COUNT,
CHANGE_INFO
} from './constants';
// 利用 action 来批改状态
// const addAction = {type:ADD_COUNT, num: 1};
// const subAction = {type:SUB_COUNT, num: -1};
export const addAction = (num) => {return { type: ADD_COUNT, num: num};
};
export const subAction = (num) => {return { type: SUB_COUNT, num: num};
};
export const changeAction = (info) => {return { type: CHANGE_INFO, info: info};
};
// 定义 getUserInfo 办法,在这个办法中发送网络申请并派发工作
export const getUserInfo = (dispatch, getState)=>{
// 发送 GET 申请
fetch('http://localhost:7001/info')
.then((response) => {
// 转换为 json 格局
return response.json();})
.then((data) => {// console.log(data);
// dispatch:派发工作
dispatch(changeAction(data));
})
.catch((error) => {console.log(error);
})
}
components/About.js
import React from 'react';
import {connect} from 'react-redux';
import {addAction, getUserInfo} from '../store/action';
class About extends React.PureComponent {
// componentDidMount:曾经挂载(渲染实现)时,react 会主动调用该办法
componentDidMount() {
// 调用 changeInfo 办法
this.props.changeInfo();}
render() {
return (
<div>
{/* 3. 通过 props 来应用 redux 中保留的数据 */}
<p>{this.props.count}</p>
<button onClick={() => { this.props.increment() }}> 递增 </button>
<p>{this.props.info.name}</p>
<p>{this.props.info.age}</p>
</div>
)
}
}
// 1.mapStateToProps 办法:通知 React-Redux, 须要将 store 中保留的哪些数据映射到以后组件的 props 上
const mapStateToProps = (state) => {
return {
count: state.count,
info: state.info
}
}
// 2.mapDispatchToProps 办法:通知 React-Redux, 须要将哪些派发的工作映射到以后组件的 props 上
const mapDispatchToProps = (dispatch) => {
return {increment() {dispatch(addAction(1));
},
changeInfo() {// dispatch(changeAction(info));
// 留神点: 默认状况下 dispatch 办法只能接管一个对象
// 如果想让 dispatch 办法除了能够接管一个对象以外, 还能够接管一个办法
// 那么咱们能够应用 redux-thunk 中间件
// redux-thunk 中间件作用:
// 让 dispatch 办法能够接管一个函数, 让咱们在通过 dispatch 派发工作的时候去执行咱们传入的办法
// 在 dispatch 派发工作时,间接执行 getUserInfo 办法
dispatch(getUserInfo);
}
}
}
// 4.connect:关联 Home 组件与 mapStateToProps 和 mapDispatchToProps 办法
export default connect(mapStateToProps, mapDispatchToProps)(About);
7.Redux-saga 中间件
1. 什么是 Redux-saga
redux-saga 和 redux-thunk 一样,是一个 Redux 中获取存储异步数据的中间件
redux-saga 能够间接拦挡 dispatch 派发的 action, 从而实现在执行 reducer 之前执行 一些其它操作
2. 如何应用 Redux-saga
1. 装置 Redux-saga
npm install redux-saga
2. 在创立 store 时, 利用 redux-thunk 中间件
3. 在生成器函数中获取网络数据
4. 在组件中派发 action
store/store.js
import {createStore, applyMiddleware} from 'redux';
import reducer from './reducer';
import mySaga from './saga';
/*
留神点: 如果导入的是 redux-thunk, 那么返回给咱们的是一个中间件对象
如果导入的是 redux-saga, 那么返回给咱们的是一个用于创立中间件对象的办法
* */
// import thunkMiddleware from 'redux-thunk';
// 1. 导入中间件办法
import createSagaMiddleware from 'redux-saga';
// 2. 通过 createSagaMiddleware 办法,创立 saga 中间件对象
const sagaMiddleware = createSagaMiddleware();
// 3. 利用 sagaMiddleware 中间件
// 在创立 store 之前, 通过 applyMiddleware 办法, 通知 Redux 须要利用哪些中间件
const storeEnhancer = applyMiddleware(sagaMiddleware);
// 利用 store 来保留状态(state)const store = createStore(reducer, storeEnhancer);
/*
留神点: 如果是 redux-thunk, 那么在创立 store 的时候指定完中间件即可
如果是 redux-saga, 那么除了须要在创立 store 的时候指定中间件以外, 还须要手动的调用中间件的 run 办法才行
* */
// 4. 拦挡 action
// 利用传入的生成器通知 redux-saga, 须要拦挡哪些 dispatch 派发的 action
sagaMiddleware.run(mySaga);
export default store;
store/constants.js
// 定义常量
export const ADD_COUNT = 'ADD_COUNT';
export const SUB_COUNT = 'SUB_COUNT';
export const CHANGE_INFO = 'CHANGE_INFO';
export const GET_USER_INFO = 'GET_USER_INFO';
store/soga.js
import {takeEvery, put} from 'redux-saga/effects'
import {GET_USER_INFO} from './constants';
import {changeAction} from './action';
function *myHandler() {
// 获取网络数据
const data = yield fetch('http://127.0.0.1:7001/info')
.then((response) => {return response.json();
})
.catch((error) => {console.log(error);
});
// console.log(data);
// 保留获取到的数据,相当于 store.dispatch(changeAction());
yield put(changeAction(data));
}
function *mySaga() {
// 第一个参数:指定须要拦挡的 action 类型
// 第二个参数:指定拦挡到这个类型的 action 之后交给谁来解决
yield takeEvery(GET_USER_INFO, myHandler)
}
export default mySaga;
store/action.js
import {
ADD_COUNT,
SUB_COUNT,
CHANGE_INFO,
GET_USER_INFO
} from './constants';
// 利用 action 来批改状态
export const addAction = (num) => {return { type: ADD_COUNT, num: num};
};
export const subAction = (num) => {return { type: SUB_COUNT, num: num};
};
export const changeAction = (info) => {return { type: CHANGE_INFO, info: info};
};
export const getUserInfo = () => {return { type: GET_USER_INFO}
}
store/reducer.js
import {
ADD_COUNT,
SUB_COUNT,
CHANGE_INFO
} from './constants';
// 定义一个状态
let initialState = {
count: 666,
info: {}};
// 利用 reducer 将 store 和 action 串联起来
function reducer(state = initialState, action) {switch (action.type) {
case ADD_COUNT:
return {...state, count: state.count + action.num};
case SUB_COUNT:
return {...state, count: state.count - action.num};
case CHANGE_INFO:
return {...state, info: action.info};
default:
return state;
}
}
export default reducer;
components/About.js
import React from 'react';
import {connect} from 'react-redux';
import {addAction, getUserInfo} from '../store/action';
class About extends React.PureComponent {
// componentDidMount:曾经挂载(渲染实现)时,react 会主动调用该办法
componentDidMount() {
// 调用 changeInfo 办法
this.props.changeInfo();}
render() {
return (
<div>
{/* 3. 通过 props 来应用 redux 中保留的数据 */}
<p>{this.props.count}</p>
<button onClick={() => { this.props.increment() }}> 递增 </button>
<p>{this.props.info.name}</p>
<p>{this.props.info.age}</p>
</div>
)
}
}
// 1.mapStateToProps 办法:通知 React-Redux, 须要将 store 中保留的哪些数据映射到以后组件的 props 上
const mapStateToProps = (state) => {
return {
count: state.count,
info: state.info
}
}
// 2.mapDispatchToProps 办法:通知 React-Redux, 须要将哪些派发的工作映射到以后组件的 props 上
const mapDispatchToProps = (dispatch) => {
return {increment() {dispatch(addAction(1));
},
changeInfo() {
// 在 dispatch 派发工作时,间接执行 getUserInfo 办法
// dispatch(getUserInfo);
// dispatch 接管一个对象
dispatch(getUserInfo());
}
}
}
// 4.connect:关联 Home 组件与 mapStateToProps 和 mapDispatchToProps 办法
export default connect(mapStateToProps, mapDispatchToProps)(About);
Redux-saga 补充:
takeEvery 和 takeLatest 区别在于:是否可能残缺的执行监听办法
对于 takeEvery 而言, 每次拦挡到对应类型的 action, 都会残缺的执行监听办法
对于 takeLatest 而言, 每次拦挡到对应类型的 action, 都不能保障肯定可能残缺的 执行监听办法
store/saga.js
-
-
-
import {takeEvery, takeLatest, put, all} from 'redux-saga/effects' import {GET_USER_INFO, ADD_COUNT, SUB_COUNT} from './constants'; import {changeAction} from './action'; function *myHandler() { // 获取网络数据 const data1 = yield fetch('http://127.0.0.1:7001/info') .then((response)=>{return response.json(); }) .catch((error)=>{console.log(error); }); const data2 = yield fetch('http://127.0.0.1:7001/info') .then((response)=>{return response.json(); }) .catch((error)=>{console.log(error); }); /* 如果咱们只须要保留一个数据, 那么间接通过 yield put 即可 然而如果咱们想同时保留多个数据, 那么咱们就必须借助另外一个函数:all() * */ // yield put(changeAction(data)); yield all([yield put(changeUserAction(data1)), yield put(changeInfoAction(data2)), yield put({type:'CHANGE_USER_NAME', name: data1.name}), yield put({type:'CHANGE_USER_Age', name: data1.age}), ]) console.log('执行到了监听办法的最初', data); } function *mySaga() { /* takeEvery 和 takeLatest 区别: 是否可能残缺的执行监听办法 对于 takeEvery 而言, 每次拦挡到对应类型的 action, 都会残缺的执行监听办法 对于 takeLatest 而言, 每次拦挡到对应类型的 action, 都不能保障肯定可能残缺的执行监听办法 例如: 间断派发了 3 次 GET_USER_INFO 的 action 那么对于 takeEvery 而言, myHandler 就会被残缺的执行 3 次 那么对于 takeLatest 而言, 如果派发下一次同类型 action 的时候 上一次派发的 action 还没有解决完, 也就是上一次的监听办法还没有解决完 那么 takeLatest 会放弃还没有解决完的代码, 间接开始解决下一次的 action * */ // yield takeEvery(GET_USER_INFO, myHandler) // yield takeLatest(GET_USER_INFO, myHandler) /* 如果咱们只须要拦挡一个类型的 action, 那么间接通过 yield takeEvery / yield takeLatest 即可 然而如果咱们想同时拦挡多个类型的 action, 那么咱们就必须借助另外一个函数: all() * */ yield all([yield takeEvery(GET_USER_INFO, myHandler), yield takeEvery(ADD_COUNT, myHandler), yield takeEvery(SUB_COUNT, myHandler), ]); }
-
-
————————————————
版权申明:本文为 CSDN 博主「奋斗吧,青年!」的原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接及本申明。原文链接:https://blog.csdn.net/lilygg/article/details/118256153