Redux 学习笔记03——Redux 的使用实例

900 阅读7分钟

实例介绍

这篇博客中我们还是以图书馆为例子,介绍如何在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中的数据,需要走一个流程:

  1. 发送一个Action请求
  2. 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的小白。另外笔记中有错误之处,还希望各位大佬予以指正。