文章目录
- 一、基本使用
- 二、案例分析:
- 2.1、不用 Redex
- 2.2、使用 Redex
- 2.3、知识点总结:
- 2.4、Redux 异步操作
- 2.5、改写计数器
- 2.6、知识点总结
一、基本使用
在组件中展示一个按钮,点按钮后给redux中的数字+9,数字初始为0,。实现一个计数器的效果
步骤:
1. 创建store
2. 创建视图组件(展示store中的数据)
3. 修改
4. 回显示数据到视图组件
-
src
下的目录结构:
src/store/index.js
/*
redux的注意点:
1. 安装好redux之后,系统不会给我们生成redux对应的src/store文件夹,需要自建
2. src/store下的index.js文件也是需要自建的
3. 创建仓库的配置文件index.js中的代码
步骤:
a. 创建默认的数据源 defaultState 变量,其值形式是一个对象
b. 先创建简单的reducer,简单的返回state即可(供页面组件使用),此时不具备修改数据的功能
c. 创建store对象
d. 导出store对象
*/
// 导入需要的依赖成员,如果传递多个reducer,则需要导入combineReducers合并reducer
import {combineReducers, createStore}from "redux"
// a. 创建默认的数据源(类似于vuex的state)
const defaultState = {
count : 0,
// 演示使用
age:20,
username:"Roze"
};
// b. 先创建简单的reducer,要求纯函数
// state一般需要进行一个赋值操作
function reducer(state = defaultState,action){
// 主要是进行数据修改
// 在修改操作的时候需要使用分支判断
if(action.type==="mod_count"){
// 修改count数据(注意数据的合并,...state展开在赋值)
return{...state,count:state.count+action.payload}
}
// 如果有乘除操作
if(action.type==="cheng_count"){
// 。。。。。
}
// 返回数据给页面使用
return state;
}
// 一个reducer负责一个数据的修改
function reducer2(state = defaultState,action){
if(action.type==="mod_age"){
return{...state,age:state.age+action.payload}
}
return state;
}
// 合并reducer
const reducers = combineReducers({reducer,reducer2})
// c. 创建store对象(通过createState方法),目前(后续有变)其参数就是reducer
const store = createStore(reducers);
// d. 导出store对象
export default store;
src/components/Counter.jsx
import React, { Component } from 'react';
// 需要导入store
import store from "../store/index";
class Counter extends Component {
// 在constructor中获取store中的数据
constructor(props){
super(props);
// 获取store数据(一次性,不具备响应式)
this.state = store.getState();
// 订阅数据的更新
store.subscribe(()=>this.setState(()=>store.getState()));
}
render() {
return (
<div>
<div>当前Store中的数据是:{this.state.reducer.count}</div>
<button onClick={this.addCount.bind(this)}>点击+9</button>
<hr/>
<div>当前Store中的数据是:{this.state.reducer2.age}</div>
<button onClick={this.addAge.bind(this)}>点击+1</button>
</div>
);
}
// 点击+9
addCount(){
// 描述数据如何更改的对象,其必须有type属性
let action ={type:"mod_count",payload:9}
// 通过storethis.$store.dispatch去派发action(会将该action派发给所有的reducer,每个reducer都会被执行,因此一定要注意type的取值)
store.dispatch(action);
}
// 点击+1
addAge(){
let action ={type:"mod_age",payload:1}
store.dispatch(action);
}
}
export default Counter;
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/Counter';
ReactDOM.render(
<App />,
document.getElementById('root')
);
- 运行结果:
- 针对redux的模块化,在一个常规项目中会将其代码拆分成以下几个部分:
1. States:
建立同名目录,存放模块化之后的state(默认数据源)
2. Reducers:
建立同名目录,存放模块化之后的reducer
3. Actions:
建立同名目录,存放模块化之后的action
4. Type(可选):
建立同名目录,存放独立的type声明
- 注意:在整个项目中,对于不同数据源的更改时使用的type名称不能重复,这个一定要注意。
- 原因:redux在做修改数据的时候,其原理是依据type的值去循环每个reducer,找到匹配的去执行,为了避免出现同名,建议type集中书写
- 具体实现,以项目的代码为准。
由于代码已经经过模块化,在获取redux中的数据的时候需要更改获取方式,比如说之前获取count是写成:this.state.count,模块化之后需要写成:this.state.counter.count,比之前多了一个模块化的模块名称(等同于vuex中命名空间)
二、案例分析:
- 我们写一个计数器
-
+
:点击后 +1 -
-
:点击后 -1 -
++
:点击后延时后 +1
2.1、不用 Redex
-
App.jsx
文件
import React ,{Component} from 'react';
class App extends Component{
constructor(props) {
super(props);
this.state = {
count:0
}
}
increment=()=>{
const {count} = this.state;
this.setState({
count :count+1
})
}
decrement=()=>{
const {count} = this.state;
this.setState({
count :count-1
})
}
incrementAsync=()=>{
const {count} = this.state;
setTimeout(()=>{
this.setState({
count :count+1
})
},1000)
}
render() {
return (<>
<h2>count:{this.state.count}</h2>
<p>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementAsync}>+</button>
</p>
</>)
}
}
export default App;
-
index.js
文件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
2.2、使用 Redex
- src 下的文件目录结构:
-
store.js
文件
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,创建Store
import {createStore} from 'redux';
// 引入为Count组件服务的reducer
import countReducer from './count_reducer';
// 暴露store
export default createStore(countReducer);
/*
const store = createStore(countReducer);
export default store;
*/
-
count_reducer.js
文件
/*
该文件是用于创建一个为Count组件服务的reducer
reducer的本质就是一个函数,reducer函数会接收到两个参数
分别是:之前的状态(preState),动作对象(action)
*/
// 引入constant文件,改写type值
import { INCREMENT, DECREMENT } from './constant';
// 初始化状态
const initState = 0;
// 定义好reducer函数后并暴露出去
export default function countReducer(preState=initState,action){
console.log(preState,action);
// 从action对象中取出:type、data
const {type,data} = action;
// 根据type决定如何加工数据
switch(type){
// case 'increment': // 原始的
case INCREMENT: // 改写后
return preState+data; // 如果是加
// case 'decrement': // 原始的
case DECREMENT: // 改写后
return preState-data; // 如果是减
// reducer是个纯函数,不能处理什么异步加+1什么的
default:
return preState;
}
}
-
constant.js
文件
/*
该模块是用于定义action对象中的type类型的常量值
*/
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";
-
count_action.js
文件
/*
该文件专门为 Count 组件生成 action 对象
*/
// 引入constant文件,改写type值
import { INCREMENT, DECREMENT } from './constant';
// 改写后
export const createIncrementAction = (data)=>{
return {type:INCREMENT,data:data}
}
export const createDecrementAction = data=>({type:DECREMENT,data:data})
/*
// 原始数据
export const createIncrementAction = (data)=>{
return {type:'increment',data:data}
}
export const createDecrementAction = data=>({type:'decrement',data:data})
*/
-
Count.jsx
组件
import React ,{Component} from 'react';
// 引入store,用于获取redux中保存的状态
import store from './redux/store';
// 引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction} from './redux/count_action';
class Count extends Component{
constructor(props) {
super(props);
this.state = {
text:'自己独有的text'
}
}
/*
如果写在这里,如果有多个组件就要写多个这个,十分麻烦
为了方便,我们把改写index.js文件,一劳永逸
componentDidMount(){
// 检测redux中状态的变化,只要变化,就检测render
store.subscribe(()=>{
this.setState({})
})
}
*/
// 加法
increment=()=>{
// 通知redux
// store.dispatch({type:'increment',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createIncrementAction(1));
}
// 减法
decrement=()=>{
// store.dispatch({type:'decrement',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createDecrementAction(1));
}
// 延时加法
incrementAsync=()=>{
setTimeout(()=>{
// store.dispatch({type:'increment',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createIncrementAction(1));
},1000)
}
render() {
return (<>
{/* 获取store中的状态值 */}
<h2>count:{store.getState()}</h2>
<p>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementAsync}>++</button>
</p>
</>)
}
}
export default Count;
-
index.js
文件
import React from 'react';
import ReactDOM from 'react-dom';
import Count from './Count';
// 为了store.subscribe调用store
import store from './redux/store';
// 简单写法:
let render = () =>
ReactDOM.render(
<Count />,
document.getElementById('root')
);
render();
//检测 store是否有变化,如果变化,自动调用render
store.subscribe(render);
/*
复杂写法:
ReactDOM.render(
<Count />,
document.getElementById('root')
);
// 检测 store是否有变化,如果变化,自动调用render
store.subscribe(()=>{
ReactDOM.render(
<Count />,
document.getElementById('root')
);
})
*/
2.3、知识点总结:
store.js:
1. 引入 redux 中的 createStore() 函数,创建一个 store
2. createStore() 调用时要传入一个为其服务的 reducer
3. 记得暴露 store 对象(export default)
count_reducer.js:
1. reducer 的本质是一个函数,接收:preState,action。返回加工后的状态
2. reducer 有两个作用:初始化状态,加工状态
3. reducer 被第一次调用时,是store自动触发
传递的preState是undefined
传递的action是 {type:'@@REDUX/INIT_a.w.e.4'}
index.js:
- 检测store中状态的改变,一旦发生改变重新渲染<Count/>组件
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
后续完善:
1. 新增 count_action.js
- 专门用于创建 action 对象
2. 新增 constant.js
- 放置由于写错的 action 对象中的 type 值
2.4、Redux 异步操作
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
- 当 action 为对象时(Object)则是同步的
- 当 action 为函数时(function)就是异步的
常见的异步库:
- Redux-thunk【推荐使用】
- Redux-saga
- Redux-effects
- Redux-side-effects
- Redux-loop
- Redux-observable
- …
基于Promise的异步库:
- Redux-promise
- Redux-promises
- Redux-simple-promise
- Redux-promise-middleware
- …
2.5、改写计数器
- 安装 redux-thunk
yarn add redux-thunk
或:
npm i -S redux-thunk
- 修改
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,创建Store
// 为了实现异步action,需要额外引入applyMiddleware
import {createStore,applyMiddleware} from 'redux';
// 引入为Count组件服务的reducer
import countReducer from './count_reducer';
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk';
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk));
- 修改
count_action.js
/*
该文件专门为 Count 组件生成 action 对象
*/
// 引入constant文件里面的常量
import { INCREMENT, DECREMENT } from './constant';
import store from './store';
// 同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = (data)=>{
return {type:INCREMENT,data:data}
}
export const createDecrementAction = data=>({type:DECREMENT,data:data})
// 异步action,就是指action的值为函数,异步action中一般会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction =(data,time)=>{
return ()=>{
setTimeout(()=>{
// store.dispatch({type:'increment',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createIncrementAction(1));
},time)
}
}
- 修改
Count.js
import React ,{Component} from 'react';
// 引入store,用于获取redux中保存的状态
import store from './redux/store';
// 引入actionCreator,专门用于创建action对象
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from './redux/count_action';
class Count extends Component{
constructor(props) {
super(props);
this.state = {
text:'自己独有的text'
}
}
// 加法
increment=()=>{
// 通知redux
// store.dispatch({type:'increment',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createIncrementAction(1));
}
// 减法
decrement=()=>{
// store.dispatch({type:'decrement',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createDecrementAction(1));
}
// 延时加法
incrementAsync=()=>{
store.dispatch(createIncrementAsyncAction(1,1000));
/*
// 把下面这个放到action里面
setTimeout(()=>{
// store.dispatch({type:'increment',data:1}); // 原来的写法
// 使用action改写
store.dispatch(createIncrementAction(1));
},1000)
*/
}
render() {
return (<>
{/* 获取store中的状态值 */}
<h2>count:{store.getState()}</h2>
<p>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementAsync}>++</button>
</p>
</>)
}
}
export default Count;
2.6、知识点总结
- 明确需求:延时的动作不想交给组件自身,想交给action
- 何时需要异步action:想要对自身状态进行操作,但是具体的数据靠异步任务返回
- 具体编码:
1. 安装:npm i -S redux-thunk
2. 配置:在store中进行配置
- import {createStore,applyMiddleware} from 'redux';
- import thunk from 'redux-thunk';
- export default createStore(countReducer,applyMiddleware(thunk));
3. 创建action的函数不再返回一个一般对象,而是一个函数,该函数中写异步任务
export const createIncrementAsyncAction =(data,time)=>{
return ()=>{
setTimeout(()=>{
store.dispatch(createIncrementAction(1));
},time)
}
}
4. 异步任务有结果后,派发一个同步的action去真正操作数据
store.dispatch(createIncrementAction(1));
备注:
- 异步action不是必须要写的
- 完全可以自己等待异步任务的结果再去派发同步action
- (就是在组件里面写,不单独拿到action去做)