写在前面
文章为了更清晰的让大家了解到redux的用法,所以会一步一步教大家redux整个过程,由浅入深,更适合新手,老司机可以直接超车~
温馨提示:项目基于TypeScript环境搭建,大部分都使用ES6语法编写示例代码,还不会的就把类型检查忽略,也可以去官网了解下噢,可以试着接触下
1.下载redux需要的依赖,前面只会用到redux,这里一块下了
$ npm install redux react-redux
2.依赖下载好之后,在项目src目录下创建一个文件夹,本着顾名思义的开发原则这里命名为redux,大家可以自由命名(如果项目不需要你维护的话😄)
3.在刚刚新建的redux文件夹里面新建一个store.ts文件,下面就是最简单的一个store原型了
//store.ts
import { createStore } from 'redux'const initState = { name: 'yydbb', age: 18}//reducer是一个纯函数,这里且将纯函数理解为入参相同,返回值就一定相同,具体定义请大家移步官方文档const reducer = (state = initState, action: any) => { return state}const store = createStore(reducer)export default store;
4.我猜大家看到上面示例代码的第一反应肯定是:就这???是不是超简单?是的,就是这么简单,下面来康康具体用法
reducer是一个纯函数,它接收两个参数,第一个是全局初始化state,第二个是action,action是一个对象,里面必须包括type,还可以附带其他参数用来修改state,最后会返回一个新的state,在我们不懈努力之后,示例代码如下:
import { createStore } from 'redux'const initState = { name: 'yydbb', age: 18}//reducer是一个纯函数,这里且将纯函数理解为入参相同,返回值就一定相同,具体定义请大家移步官方文档const reducer = (state = initState, action: any) => {
if (action.type === 'CHANGE_NAME') { let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样 myState.name = action.nameVal return myState } return state}const store = createStore(reducer)export default store;
相比前一步的代码,康康我们增加了些什么
我们写了一个看不懂的东西,使用过typescript的小伙伴们就不用多说,这里解释一下,这里我们定义了一个接口,约束了reducer第二个参数action的数据类型,如果项目中其他开发要使用reducer,他就必须按照这个格式传参,不然咱就不给他通过,让他自己找bug去
interface Action{
type: string,
ageVal:number
}
接下来我们拓展了一下reducer函数的处理逻辑,相信这里面的代码大家理解起来都没问题(有问题的就回去学学JavaScript的基本语法)
const reducer = (state = initState, action: Action) => {
if (action.type === 'CHANGE_NAME') {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.name = action.nameVal
return myState
}
if (action.type === 'CHANGE_AGE') {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.age--
return myState
}
return state
}
其实到这一步,redux就可以用起来了,你没听错,让我们来试试吧
5.接下来我们就要在页面中使用一下刚刚写好的redux,期不期待😄
- 在Login.tsx页面中引入redux
import React from "react";
import store from './../redux/store'
class Login extends React.PureComponent<any, any> { state = { age: 0, name: 'enen' } componentDidMount() { const { name, age } = store.getState() this.setState({ age, name }) } public render() { return ( <div> 我是{this.state.name}我今年{this.state.age} </div> ); }}
- store里面关键API就是getState(),这个方法拿到的就是我们store.ts文件夹里面定义的初始化initState的值,最后在页面上的效果为:
- 现在我们的redux已经能够在页面上应用了,但是仅仅只是能用,不能修改,聪明的小伙伴已经开始琢磨了吧,接下来一起来康康怎么去修改它,在我们加班加点修改之后,Login.tsx页面变得不认识了
class Login extends React.PureComponent<any, any> {
state = {
age: 0,
name: 'enen'
}
componentDidMount() {
const { name, age } = store.getState()
this.setState({
age,
name
})
}
public render() {
return (
<div>
我是{this.state.name}我今年{this.state.age}
<button onClick={this.changeName}>修改名字</button>
<button onClick={this.changeAge}>修改年龄</button>
</div>
);
}
changeName = () => {
const action = {
type: 'CHANGE_NAME',
nameVal: '开心'
}
store.dispatch(action)
console.log(store.getState());
}
changeAge = () => {
const action = {
type: 'CHANGE_AGE'
}
store.dispatch(action)
console.log(store.getState());
}
}
贴上我们修改后的store.ts
import { createStore } from 'redux'
const initState = {
name: 'yydbb',
age: 18
}
interface Action {
type: string,
ageVal?: number,
nameVal?: string
}
//reducer是一个纯函数,这里且将纯函数理解为入参相同,返回值就一定相同,具体定义请大家移步官方文档
const reducer = (state = initState, action: Action) => {
if (action.type === 'CHANGE_NAME') {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.name = action.nameVal
return myState
}
if (action.type === 'CHANGE_AGE') {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.age--
return myState
}
return state
}
const store = createStore(reducer)
export default store;
页面操作效果如下:
我们一共操作了五次,一次changeName,四次changeAge,如图大家看到打印的结果是我们想要的,但是界面展示的结果并不是我们想要的,这是为什么呢?我们不是改了好几次了吗,为什么界面不响应啊,Vuex比这个好多了,我提交一个commit界面马上就能看到更新后的值了,redux怎么可能没有,我们能想到的官方怎么可能想不到😄,还是在Login.tsx文件中,我们仅仅修改了一个地方,如下:
componentDidMount() {
store.subscribe(() => {
const { name, age } = store.getState()
this.setState({
age,
name
})
})
}
这里我们先不管为什么这样写先康康效果:
这么神奇?再回来康康我们修改的地方,你会发现subscribe这个神秘的单词,复制到百度翻译(没有打广告的意思,大家喜欢啥就用啥)去看看是啥意思,原来是订阅的意思,那这里我们就可以理解为页面获取redux数据的同时也订阅了它,这种好处就是之后如果数据发生改变了,页面就可以看到最新的数据了,缺点就是它相当于Vue里面的watch,如果不注销会一直在内存中消耗资源,所以subscribe这个函数设计的时候就想好了,它的返回值就是一个注销函数,执行就行了,当然暂时先不建议用它,附上用法:
let unsubscribe =
store.subscribe(() => {
const { name, age } = store.getState()
this.setState({
age,
name
})
})
// unsubscribe()
小结:
到这里我们已经对Redux最简单的用法有所了解了,下面我们将对store.ts进行拆分,做一个小小的优化,下面是我们优化过后的文件目录:
可以看到多了一个reducer.ts文件,一个actionType.ts文件,reducer.ts文件内容如下:
import { CHANGE_NAME, CHANGE_AGE } from './actionType'
const initState = {
name: 'yydbb',
age: 18
}
interface Action {
type: string,
ageVal?: number,
nameVal?: string
}
//reducer是一个纯函数,这里且将纯函数理解为入参相同,返回值就一定相同,具体定义请大家移步官方文档
const reducer = (state = initState, action: Action) => {
if (action.type === CHANGE_NAME) {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.name = action.nameVal
return myState
}
if (action.type === CHANGE_AGE) {
let myState = JSON.parse(JSON.stringify(state))//粗暴的深拷贝,真实项目中不建议大家这样
myState.age--
return myState
}
return state
}
export default reducer
actionType.ts文件内容如下:
export const CHANGE_NAME = 'CHANGE_NAME'
export const CHANGE_AGE = 'CHANGE_AGE'
actionType.ts主要目的是将type常量标准化,减少bug率,哪里用到这些常量,引用就行
拆分之后的store.ts文件内容如下:
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store;
是不是一下开心起来了,简直不敢想,四行代码?(这里补充一点一个项目中只能有一个store)
6.到这里大家有没有发现哪里不对,没错还有一个比较明显的问题,就是如果想要在每个页面都用到store,那就得每个页面都要引用,这个时候前面下载的react-redux就要上场了,话不多说,动动小手~
下面是重新写好的index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux'
import store from './redux/store'
import "./index.css";
import AppRouter from "./router/router";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
两个地方:
- 从react-redux中引入Provider组件
- 引入store
- 将Provider组件包裹页面路由把store作为参数传递给所有路由
- 接下来就可以在每个页面使用store的数据且不用引入store
下面是重新写好的Login.tsx
import React from "react";
import { connect } from 'react-redux'
class Login extends React.PureComponent<any, any> {
state = {
age: 0,
name: 'enen'
}
componentDidMount() {
const { name, age } = this.props
this.setState({
age,
name
})
}
componentDidUpdate() {
console.log(this.props.name, this.props.age);
}
public render() {
return (
<div>
我是{this.state.name}我今年{this.state.age}
<button onClick={this.props.changeName}>修改名字</button>
<button onClick={this.props.changeAge}>修改年龄</button>
</div>
);
}
}
const mapStateToProps = (state: any) => {
return {
age: state.age,
name: state.name
}
}
const mapDispathToProps = (dispatch: any) => {
return {
changeAge() {
const action = {
type: 'CHANGE_AGE'
}
dispatch(action)
},
changeName() {
const action = {
type: 'CHANGE_NAME',
nameVal: '开心'
}
dispatch(action)
}
}
}
export default connect(mapStateToProps, mapDispathToProps)(Login)
- 重点是connect这个API,它接收两个参数,如上图,顾名思义,将store中的state和reducer分别映射到props里面,现在我们把两个dispath操作都写到mapDispathToProps 里面来了,这使得我们页面逻辑变少,更加清晰了。对于这种用法大家一定要多写写,一时间理解不了,先学会用,后再深入了解
- 我们在生命周期componentDidUpdate里面打印修改后的值,发现是实时的,但是页面没有实时更新
找到一个比较简单粗暴(更优解决方案笔者也在摸索)的解决方案,在componentDidUpdate生命周期里面再次给name和age赋值,可以达到预期效果,也可以直接使用props,这是实时的,这里赋值是为了后面可能会有别的操作
componentDidUpdate() {
const { name, age } = this.props
this.setState({
age,
name
})
console.log(this.props.name, this.props.age);
}
效果如下:
大家可以发现,每个操作都打印了两次,那是因为每次修改页面的state,页面会触发componentDidUpdate生命周期,所以选择在这里处理监听以达到subscribe同样的效果
写在最后:
- 相信看完这边文章,小伙伴们对redux用法应该能掌握的差不多了
- redux还有很多插件,插件只是为了拓展功能,大家有兴趣的可以自己去琢磨,爱折腾才能不断进步哦~
- 排版有些乱了不好意思,觉得还不错的大家点个赞~😄