😎😎欢迎来到我的博客😎😎 📔博主是一名大学在读本科生,主要学习方向是前端😊。 🍭目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏🤩 🛠目前正在学习的是🔥
R
e
a
c
t
框架
React框架
React框架🔥,中间穿插了一些基础知识的回顾?? 🌈博客主页👉codeMak1r.的博客
本文被专栏【React–从基础到实战】收录
🕹坚持创作??,一起学习📖,码出未来👨🏻?💻! (当然不是这张图)
Redux理解
redux是什么
- redux是一个专门用于做状态管理的JS库(不是react插件库);
- 它可以在react、angular、vue等项目中,但基本与react配合使用;
- 作用:集中式管理react应用中多个组件共享的状态。
什么情况下需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享);
- 一个组件需要改变另一个组件的状态(通信);
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用。
redux工作流程🌟图
action
- 动作的对象
- 包含两个属性:
- type:标识属性,值为字符串,唯一,必要属性;
- data:数据属性,值类型任意,可选属性。
- 例子:{ type:‘ADD_STUDENT’, data:{name: ‘tom’,age: 18} }
reducer
- 用于初始化状态、加工状态;
- 加工时,根据旧的 state 和 action,产生新的 state 的纯函数;
- 初始化时,previousState 为 undefined。
store
-
将state 、action 、reducer联系在一起的对象; -
如何得到此对象? import { createStore } from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
-
此对象的功能?
- getState():得到 state;
- dispatch(action):分发 action,触发 reducer 调用,产生新的 state;
- subscribe(listener):注册监听,当产生了新的 state 时,自动调用。
select选择框内有三个数组1、2、3可选,选择1后点击“加”即和加1; 第三个按钮为和为奇数时,则加;和为偶数时,则不变; 最后的“异步加”则是用setTimeout模拟了一个异步环境,点击“异步加”等待0.5s再加。
求和案例——纯react版
项目目录:
src
├─App.jsx
├─index.js
├─components
| ├─Count
| | └index.jsx
public
└index.html
使用create-react-app创建脚手架项目,项目目录如上所示。
纯react版是通过本组件的state获取数据,在此不过多赘述。 欢迎订阅本专栏【React–从基础到实战】学习react知识,持续更新中!
App.jsx
import React, { Component } from 'react'
import Count from './components/Count'
export default class App extends Component {
render() {
return (
<div>
<Count />
</div>
)
}
}
src/component/Count/index.jsx
import React, { Component } from 'react'
export default class Count extends Component {
state = { count: 0 }
increment = () => {
const { value } = this.selectedNumber
const { count } = this.state
this.setState({ count: count + parseInt(value) });
}
decrement = () => {
const { value } = this.selectedNumber
const { count } = this.state
this.setState({ count: count - value });
}
incrementIfOdd = () => {
const { value } = this.selectedNumber
const { count } = this.state
if (count % 2 !== 0) {
this.setState({ count: count + parseInt(value) });
}
}
incrementAsync = () => {
const { value } = this.selectedNumber
const { count } = this.state
setTimeout(() => {
this.setState({ count: count + parseInt(value) });
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<select ref={currentNode => { this.selectedNumber = currentNode }}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>加</button>
<button onClick={this.decrement}>减</button>
<button onClick={this.incrementIfOdd}>和为奇数时,加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
求和案例——redux精简版
为了让大家更好的理解redux基本原理,我们先仅使用少数的api,用过一个精简版的redux去完成这个求和案例,最后我们会用完整版的redux去完成这个求和案例。学东西嘛,得循序渐进,不能一口吃成大胖子不是?
首先,我们需要下载redux:
npm install redux
或
yarn add redux
根据原理图,我们必不可少的肯定是需要store这个对象,以及reducer这个函数的。
store对象负责存储state,reducer负责初始化和加工state,都是必不可少的。
1、创建store.js
import { legacy_createStore as createStore } from 'redux';
import countReducer from './count_reducer'
export default createStore(countReducer)
2、创建count_reducer.js
const initState = 0
export default function countReducer(prevState = initState, action) {
console.log(prevState, action)
const { type, data } = action
switch (type) {
case 'increment':
return prevState + data;
case 'decrement':
return prevState - data;
default:
return prevState
}
}
3、回到Count组件中,我们先试着获取到store中存储的数据。
import store from '../../redux/store'
···
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
···
现在我们把状态(state)交给redux管理了,当然就不需要在Count组件中初始化状态了,也就不需要再到this.state中结构赋值count变量了。
我们只需要调用redux的api:store.getState() 就能够拿到存储在store中的状态。
打开界面一看:
果然是拿到了状态state,那么我们就着手依次进行计算操作了~
首先是,点击“加”按钮,触发this.increment方法,我们在方法内部只需要调用redux的api:dispatch()即可。
increment = () => {
const { value } = this.selectedNumber
store.dispatch({ type: 'increment', data: parseInt(value) })
}
我们通过调用dispatch()方法,传入一个我们自己定义的action动作对象,进行“加”的动作,这个动作对象会由store传给reducer函数,函数内部判断type值为‘increment’,那么就会对这个‘和’进行“加”的动作
代码实现完了,我们看一下效果:
完犊子,这怎么没加上去呢?
我们在count_reducer中console一下,找找原因,
打开控制台,我们发现状态是在跟随我们的点击随之改变的,但是页面并没有同步这个状态的改变。换句话说,也就是状态改变了,页面并没有随着状态的改变而重新渲染。在之前的纯react版本中,我们是通过this.setState() 进行状态更新的,我们都知道react中,setState可以驱动视图更新,但是我们现在并不在组件的state中管理状态,而是通过redux进行状态的管理,所以才会导致页面没有更新。
我们需要手动监测redux中store状态,当状态发生变化时,我们自己去手动调用render,就可以解决页面不更新的问题。
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
我们可以使用这个api:store.subscribe() 这个函数的参数接受一个回调函数,只要redux中的任何状态发生了改变,都会调用这个回调函数。我们可以在这个回调函数内部手动调用this.setState({}) ,只要这么调用了,就会自动重新render,这样就可以实现页面的更新了。
Count组件源码:
import React, { Component } from 'react'
import store from '../../redux/store'
export default class Count extends Component {
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
increment = () => {
const { value } = this.selectedNumber
store.dispatch({ type: 'increment', data: value * 1 })
}
decrement = () => {
const { value } = this.selectedNumber
store.dispatch({ type: 'decrement', data: value * 1 })
}
incrementIfOdd = () => {
const { value } = this.selectedNumber
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch({ type: 'increment', data: value * 1 })
}
}
incrementAsync = () => {
const { value } = this.selectedNumber
setTimeout(() => {
store.dispatch({ type: 'increment', data: value * 1 })
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
<select ref={currentNode => { this.selectedNumber = currentNode }}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>加</button>
<button onClick={this.decrement}>减</button>
<button onClick={this.incrementIfOdd}>和为奇数时,加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
redux精简版总结:
-
去除Count组件自身状态state; -
src下建立: /src/redux/store.js /src/reudx/count_reducer.js -
store.js 引入redux中的legacy_createStore函数,创建一个store; legacy_createStore调用时要传入一个为其服务的reducer; 默认暴露store对象 -
count_reducer.js reducer本质是一个函数,接收:preState,action作为参数,返回加工后的状态; reducer有两个作用:初始化状态,加工状态; reducer被第一次调用,是store自动触发的,传递的preState是undefined -
状态改变后手动调用setState()以达到页面实时渲染的效果。
求和案例——redux完整版
在精简版中,我们使用的action对象是我们自己手动传入的action动作对象,但是redux有更好的api,来帮助我们自动创建action动作对象,不需要手动输入。我们来看看redux完整版是怎么实现集中状态管理的吧~
基于精简版,我们还需要在redux文件夹下创建一个文件
/src/redux/count_action_creator.js,该文件专门用于为Count组件生成action动作对象。
count_action_creator.js
export const createIncrementAction = data => ({ type: 'increment', data })
export const createDecrementAction = data => ({ type: 'decrement', data })
在Count组件中引入文件以及暴露的函数:
import {
createIncrementAction,
createDecrementAction
} from '../../redux/count_action_creator'
在所有需要使用dispatch分派action动作对象时,我们不再使用之前手打的action对象了,而是直接调用函数,自动生成action对象。
例如,在点击“加”按钮方法中:
increment = () => {
const { value } = this.selectedNumber
store.dispatch(createIncrementAction(value * 1))
}
以此类推,将所有方法内手打的action对象更改为使用函数自动生成的action对象。
Count组件源码:
import React, { Component } from 'react'
import store from '../../redux/store'
import {
createIncrementAction,
createDecrementAction
} from '../../redux/count_action_creator'
export default class Count extends Component {
componentDidMount() {
store.subscribe(() => {
this.setState({})
})
}
increment = () => {
const { value } = this.selectedNumber
store.dispatch(createIncrementAction(value * 1))
}
decrement = () => {
const { value } = this.selectedNumber
store.dispatch(createDecrementAction(value * 1))
}
incrementIfOdd = () => {
const { value } = this.selectedNumber
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch(createIncrementAction(value * 1))
}
}
incrementAsync = () => {
const { value } = this.selectedNumber
setTimeout(() => {
store.dispatch(createIncrementAction(value * 1))
}, 500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
<select ref={currentNode => { this.selectedNumber = currentNode }}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>加</button>
<button onClick={this.decrement}>减</button>
<button onClick={this.incrementIfOdd}>和为奇数时,加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
这样一来,基本的redux完整版就已经写完了;但是还有一个可优化的空间,我们在各个文件中都写了’increment’和’decrement’字符串,这会带来一些问题:
- 字符串在IDE中没有变量提示,容易拼写错误;
- 不便于维护和管理,一旦要更换字符串名称,需要到各个文件中都去更改一次;
redux完整版(优化)
于是我们可以再创建一个js文件,用于常量的管理(常量维护):
/src/redux/constant.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
更改,count_action_creator.js
import { INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
更改,count_reducer.js
+ import { INCREMENT, DECREMENT } from './constant'
const initState = 0
export default function countReducer(prevState = initState, action) {
console.log(prevState, action)
const { type, data } = action
switch (type) {
- case 'increment':
+ case INCREMENT:
return prevState + data;
- case 'decrement':
+ case DECREMENT:
return prevState - data;
default:
return prevState
}
}
如此一来,有利于后续项目越来越大的时候,进行变量名的管理与维护;而且,书写字符串是没有提示的,但是向这样引入变量,书写变量是有提示的,变量书写错误的话,IDE还有相应的错误提示,更加规范。
redux完整版总结:
- count_action.js 专门用于创建action动作对象;
- constant.js 放置由于编码疏忽可能写错的action中的type。
|