Redux 的基本使用
1.核心概念1.什么是Redux?Redux是一个管理状态(数据)的容器,提供了可预测的状态管理2.什么是可预测的状态管理?数据在什么时候,因为什么,发生了什么改变,都是可以控制和追踪的,我们就称之为预测的状态管理3.为什么要使用Redux?React是通过数据驱动界面更新的,React负责更新界面,而我们负责管理数据默认情况下我们可以在每个组件中管理自己的状态,但是现在前端应用程序已经变得越来越
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
- 安装 redux-thunk
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
- 1.安装 Redux-saga
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),
]);
}
export default mySaga;
更多推荐
所有评论(0)