实例介绍
这篇博客中我们还是以图书馆为例子,介绍如何在React项目中使用Redux。如果你没有看过我之前的博客,那么这里我就再阐述一下图书馆的例子:
一个图书馆中有很多书,图书馆有管理员,我们如果想要找书的时候,可以询问管理员书的具体位置,方便我们快速拿到这本书;我们想要借书或还书的时候,可以将书交给管理员,并且出示自己的借书卡,让管理员帮助我们准确地登记,并将归还的书放在准确地位置上。
通过之前的博客,我们知道以下的对应关系:
- Store:对应的是整个图书馆
- State:对应的是图书馆某一时刻的具体状态。在这个事例中,我们假设这个State是,图书馆某一时刻的在馆图书和已经借出书籍的记录。
- Action:对应的是图书馆用户的请求,比如找书的请求,借书和还书的请求等。
- Reducer:对应的是图书馆的管理员。我们发出借书的请求,但是不知道如何登记,如果修改图书馆的相应数据。于是我们就把处理请求的工作交给图书馆管理员来做。
明白了Redux元素和图书馆实例之间的对应关系,下面我们开始动手写这个实例的具体代码。
图书馆实例的具体代码
1. 初始化图书馆的State
我们之所以要使用Redux,是因为要借助Redux来管理项目中的数据,而且这些数据一般都有一个初始值,在实际的项目中,这个初始值一般都是通过API从数据库中获取。
对于图书馆来说,肯定也是有初始值的,比如初始状态下图书馆在馆的图书和已借出书籍的记录等。所以第一步,我们需要初始化图书馆的数据,也就是初始化 State 。新建一个 initState.js 文件,代码如下:
// initState.js 文件
const initState = {
// 当前状态下,图书馆所有在馆的图书
allBooks: ["明朝那些事儿", "百年孤独", "盗墓笔记", "红楼梦", "西游记", "三国演义"],
// 当前状态下,图书馆已借出图书的借阅记录
outBooks: {
"20152008": ["法医清明", "西游记"],
"20152023": ["你好旧时光", "大秦帝国"],
"20152135": ["水浒传", "鲁迅文集"]
}
}
// 因为我们是在一个单独的文件中定义的这个变量,所以需要把这个变量导出
// 然后在其他需要用的这个变量的文件中,导入变量
export default initState;
2. 定义Reducer,对接受的的Action进行数据处理
图书馆的图书信息已经定义好了,现在就可以再来聘用一个图书馆管理员来管理图书信息。即定义一个Reducer,通过接收到的Action,对State做相应的修改。
他的工作就是接受图书馆用户发出的请求,比如:找书,借书,还书等。根据不同的请求信息,对图书馆的数据进行相关处理,然后返回处理完成后图书馆的信息。
比如用户20152135要借一本《明朝那些事儿》,他会给管理员发送一个请求,管理员接受这个请求之后,会从在馆书籍中,将《明朝那些事儿》删除,同事会把《明朝那些事儿》添加到20152135用户的借阅信息中。然后返回处理后图书馆的最新书籍状态,方便下一次的查找和借阅。
用户发出的请求对应到代码中是一个对象,上述借书请求的对象如下:
const action_2 = {
type: 'borrow_book',
title: '明朝那些事儿',
userId: '20152135'
};
现在接收到了用户发出的请求,我们就要定义一个具体的Reducer,来处理这个请求。新建 reducer.js 文件,具体定义代码如下:
// reducer.js 文件
// 因为要修改图书馆的State,所以这里要引入
import initState from './initState.js'
function reducer(state = initState, action) {
// 判断请求的type,如果是borrow_book
if(action.type === 'borrow_book') {
let bookIndex = state.allBooks.indexof(action.title);
state.allBooks.splice(bookIndex, 1);
state.outBooks[action.userId].push(action.title);
return state;
}
return state;
}
export default reducer;
通过上述代码,我们可以看到,reducer是一个纯函数,在这个函数里面,只是对State进行修改,没有其他任何副作用。而且,只要是同样的输入,必定得到同样的输出。
这里我们只定义一个处理action的逻辑,如果想要定义多个处理action的逻辑,可以直接在这个函数中进行添加。比如以下代码:
import initState from './initState.js'
function reducer(state = initState, action) {
if(action.type === 'borrow_book') {
// ...修改State的逻辑代码
return state;
}
if(action.type === 'return_book') {
// ...修改State的逻辑代码
return state;
}
if(action.type === 'search_book') {
// ...修改State的逻辑代码
return state;
}
return state;
}
export default reducer;
3. 定义Store,开始图书馆的正常营业
图书馆的图书信息已经有了,图书馆管理员已经有了,现在我们就可以开始图书馆的正常营业了。
上面提到过,图书馆对应的是Redux的Store,下面我们需要做的就是根据Redux来创建Store。定义非常简单,新建 Store.js 文件,代码如下:
// Store.js 文件
import { createStore } from 'redux' // 引入redux包中的创建函数
import reducer from './reducer' // 引入定义的reducer
const store = createStore(reducer)
export default store;
4. 引入Store,使用Redux中的数据渲染页面
到目前为止,我们已经成功创建了Store,现在就可以在React组件中引入Store,开始使用Redux管理我们的数据了。下面给出一个小实例:通过Redux中的数据,来渲染页面。直接在App.jsx 文件中进行书写代码。
import React from 'react';
import store from './Store.js'
class App extends React.Component {
constructor(props) {
super(props);
// 通过getState() 函数获取Store这一时刻的State
this.state = store.getState();
}
render() {
return (
<div>
<p>图书馆在馆图书:</p>
<ul>
{this.state.allBooks.map(book => <li key={book}>{book}</li>)}
</ul>
{
Object.keys(this.state.outBooks).map(userId => {
return (
<div key={userId}>
{userId}:
{ this.state.outBooks[userId].join(" ") }
</div>
)
})
}
</div>
);
}
}
export default App;
5. 修改Store中的数据
使用Redux管理数据的时候,一个常见的需求是修改Store中的数据。
在Redux中,我们不能直接修改Store中的数据,需要走一个流程:
- 发送一个Action请求
- Reducer接收这个请求,通过定义好的函数来修改Store中的数据
所以,想要在一个组件中修改Store的数据,我们首先需要定义一个Action对象,比如前面提到的这个Action:
const action_2 = {
type: 'borrow_book',
title: '明朝那些事儿',
userId: '20152135'
};
Action定义好之后,我们需要在组件中使用 Store.dispatch()方法来触发这个Action对象。下面展示一个非常简单的例子:
import React from "react";
import Store from "../redux/Store";
class AddNewBooks extends React.Component {
constructor(props) {
super(props);
this.state = {
allBooks: Store.getState().allBooks,
newBookName: ""
};
}
setNewBooksName = (val) => {
this.setState({
newBookName: val
});
}
addNewBooks = () => {
// 定义Action
const addBooks = {
type: "ADD_BOOKS",
newBookName: this.state.newBookName
}
// 触发Action
Store.dispatch(addBooks);
}
render() {
return (
<>
<div className="add-books-panel">
<input value={this.state.newBookName} type="text" onChange={(e) => { this.setNewBooksName(e.currentTarget.value) }} />
<button onClick={this.addNewBooks}>Add New Boos</button>
</div>
</>
)
}
}
export default AddNewBooks;
触发Action之后,Reducer中的函数会接收到这个请求,然后根据定义好的逻辑代码对Store中的书进行处理,比如这样的Reducer函数:
import InitialState from "./InitialState";
const reducer = (state = InitialState, action) => {
if (action.type === "ADD_BOOKS") {
return {
...state,
allBooks: [...state.allBooks, action.newBooks]
};
}
return state;
}
export default reducer;
Reducer函数对Store中的书处理之后,会返回一个新的state,这个state就是当前Store的一个快照,也就是当前Store中的最新数据。
如此一来,我们就完成了redux中的数据更新。
6. 监听Store数据变化,刷新页面
在项目中使用Redux的时候,还有这样一个常见的情形:当Store中的数据发生变化的时候,其他组件中的内容要能够实时更新。其实做大这一点非常简单,只需要使用 Store.subsctibe() 方法即可实现。
这个方法接受一个函数作为参数,一旦state有变化,Redux就会调用这个函数。如果你有了解过设计模式,可以直到,Redux使用到了 "发布-订阅" 模式。通过subsctibe方法订阅Store的更新,一旦Store发布了更新,Redux就会执行订阅函数中的内容。
下面来看这个方法的使用实例:
import React from 'react'
import Store from "../redux/Store";
class AllBooks extends React.Component {
constructor(props) {
super(props);
this.state = {
allBooks: Store.getState().allBooks
};
// 订阅Redux Store中的数据动态,一旦有变化,就会执行这个函数中的内容
Store.subscribe(() => {
this.setState({
allBooks: Store.getState().allBooks
});
});
}
render() {
return (
<div className="books-panel">
{
this.state.allBooks.map(_book => <p className="book-item" key={_book}>{_book}</p>)
}
</div>
)
}
}
export default AllBooks;
写在后面
好了,这就是关于这个Redux使用实例全部内容啦。希望这篇浅显的笔记能够帮助到刚刚接触Redux的小白。另外笔记中有错误之处,还希望各位大佬予以指正。