Redux从入门到进阶,看这一篇就够了!(上)

简介: 那在下面的这篇文章中,将从入门到进阶,讲解 redux 的工作流程。

20.png🌂序言


大家都知道, react单向数据流,所以它传递数据也较为简单,父子之间的关系也较为明确。但是呢,如果我们要做更多复杂数据的传递,单单使用 react 是完全不够的。因此,我们需要用到 redux 来做更为复杂的数据传递。

那在下面的这篇文章中,将从入门到进阶,讲解 redux 的工作流程。

叮!开始 redux 之旅吧~👏


☂️一、基础知识


1、Redux概念简述

对于 react  来说,它是一个非视图层的轻量级框架,如果要用它来传递数据的话,则要先父传子,然后再慢慢地一层一层往上传递。

但如果用 redux 的话,假设我们想要某个组件的数据,那这个组件的数据则会通过 redux 来存放到 store 中进行管理。之后呢,通过 store ,再来将数据一步步地往下面的组件进行传递。

值得注意的是,我们可以视 ReduxReducerFlux 的结合。


2、Redux的工作流程

Redux ,实际上就是一个数据层的框架,它把所有的数据都放在了 store 之中。我们先来看一张图:

21.png

大家可以看到中间的 store ,它里面就存放着所有的数据。继续看 store 向下的箭头,然后呢,每个组件都要向 store 里面去拿数据。

我们用一个例子来梳理整张图,具体如下:

  • ①整张图上有一个 store ,它存放着所有的数据,也就是存储数据的公共区域
  • ②每个组件,都要从 store 里面拿数据;
  • ③假设现在有一个场景,模拟我们要在图书馆里面借书。那么我们可以把 react Component 理解为借书人,之后呢,借书人要去找图书馆管理员才能借到这本书。而借书这个过程中数据的传递,就可以把它视为是 Action Creators ,可以理解为 “你想要借什么书” 这句话。
  • Action Creatures 去到 store 。这个时候我们把 store 当做是图书馆管理员,但是,图书馆管理员是没有办法记住所有图书的数据情况的。一般来说,它都需要一个记录本,你想要借什么样的书,那么她就先查一下;又或者你想要还什么书,她也要查一下,需要放回什么位置上。
  • ⑤这个时候就需要跟 reducers 去通信,我们可以把 reducers 视为是一个记录本,图书馆管理员用这个记录本来记录需要的数据。管理员 store 通过 reducer 知道了应该给借书人 Components 什么样的数据。


🎃二、使用Antd实现TodoList页面布局


1、在项目中使用Antd

打开 antdesign 的官网👉antd官网传送门,我们先来在项目中引入它。具体步骤如下:

第一步,安装 antd命令如下:

npm install antd --save
复制代码

第二步,引入样式。代码如下:

import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
复制代码


2、使用Antd实现TodoList的基本布局

首先,我们在项目的 src 文件夹下创建一个新的文件,命名为 TodoList.js具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
const data = [
  'Racing car sprays burning fuel into crowd.',
  'Japanese princess to wed commoner.',
  'Australian walks 100km after outback crash.',
  'Man charged over missing wedding girl.',
  'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input placeholder="todo info" style={{ width: '300px' }} />
          <Button type="primary">提交</Button>
        </div>
        <List
          bordered
          dataSource={data}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }
}
export default TodoList;
复制代码

此时浏览器的显示效果为:

22.png


3、创建redux中的store


(1)创建store

接下来我们来创建项目中的 store 。首先在项目的 src 文件夹下创建一个新的文件夹,命名为 store 。接着,我们在 store 文件夹下面,创建一个新的文件,命名为 index.js具体代码如下:

import { createStore } from "redux";
import reducer from './reducer';
const store = createStore(reducer);
export default store;
复制代码

然后呢,继续在 store 文件夹下面创建一个新的文件,命名为 reducer.js具体代码如下:

const defaultStore = {
  inputValue: '',
  list: []
};
export default (state = defaultStore, action) => {
  return state;
}
复制代码

由此,我们就创建完成了项目中的 store


(2)在项目中使用store

创建完 store 之后,我们先初步在项目中使用这个 store ,以便于看看效果。先来添加 store 中的数据,首先改造在 store 中的 reducer.js 文件,具体代码如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};
export default (state = defaultStore, action) => {
  return state;
}
复制代码

之后改造 TodoList.js具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 
class TodoList extends Component {
  constructor(props) {
    super(props);
      this.state = store.getState()
  }
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input placeholder={this.state.inputValue} style={{ width: '300px' }} />
          <Button type="primary">提交</Button>
        </div>
        <List
          bordered
          dataSource={this.state.list}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }
}
export default TodoList;
复制代码

此时浏览器的显示效果为:

23.png


🧵三、Action和Reducer的编写 - 增添功能


1、主体页面内容改造

接下来,我们使用 actionreducer ,来对这个组件的数据进行前后传递。首先,先来改造 TodoList.js 文件。具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 
class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState()
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleStoreChange = this.handleStoreChange.bind(this)
    this.handleBtnClick = this.handleBtnClick.bind(this)
    store.subscribe(this.handleStoreChange)
  }
  render() {
    return (
      <div style={{marginTop: '10px', marginLeft: '10px'}}>
        <div>
          <Input
            value={this.state.inputValue}
            placeholder="todo info"
            style={{ width: '300px', marginRight: '10px'}}
            onChange={this.handleInputChange}
          />
          <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
        </div>
        <List
          style={{marginTop: '10px', width: '300px'}}
          bordered
          dataSource={this.state.list}
          renderItem={item => <List.Item>{item}</List.Item>}
        />
      </div>
    )
  }
  handleInputChange(e) {
    // 在react中,action是一个对象的形式
    // type旨在告诉react说,你帮我去改变input的值,这个值是下面的value,也就是e.target.value
    const action = {
      type: 'change_input_value',
      value: e.target.value
    }
    store.dispatch(action)
    // console.log(e.target.value)
  }
  handleStoreChange() {
    // 当感知到store的数据发生变化时,那么就去调用store.getState方法,从store里面再重新取一次数据,
    // 然后去调用setState,替换掉当前store里面的数据
    this.setState(store.getState())
  }
  handleBtnClick() {
    const action = {
      type: 'add_todo_item'
    }
    store.dispatch(action)
  }
}
export default TodoList;
复制代码

接下来我们来分析以上代码。首先,每一个动作分别会先去绑定对应的事件,之后呢,在事件里面,去创造 action 。而对于创造的 action 来说,它旨在告诉 react ,让 react 去帮忙 action 去改变某个值,而这个值就是它绑定的 value

最后, action 要做的事情结束了,那么它的数据就需要去存储到 store 里面。于是通过 store.dispatch(action) 来进行处理,将 action 的数据传递到 store 里面。


2、改变action中的数据

对于 action 一开始的值来说,它是固定的。但有时候我们是想要去修改action中的值,这个时候就需要用到 reducer 。现在,我们来改造下 reducer.js 文件,让 input 框可以自由的输入值,同时,点击提交按钮之后,进行列表的增添操作。具体代码如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};
// reducer 可以接收state,但是绝不能修改state
const reducer = (state = defaultStore, action) => {
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === 'add_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  return state;
}
export default reducer;
复制代码


3、store数据改造

下面,我们来看下 store 文件夹下 index.js 的内容。我们需要对其进行简单的改造,具体代码如下:

import { createStore } from "redux";
import reducer from './reducer';
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
复制代码

除了 reducer 之外,我们还要将 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 给传递进去并调用这个方法。

最后,我们来看下浏览器的显示效果:

1.png


🧶四、使用Redux实现TodoList的删除功能


1、对组件进行事件绑定

上面我们实现了增添功能,那么现在,我们继续来实现删除功能,实现每点击每一项时,能够删除点击项的数据。先来在 TodoList.js 文件中绑定对应的事件,具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
import store from './store'; 
class TodoList extends Component {
  constructor(props) {
    // 此处省略上述已有代码
  }
  render() {
    return (
      {/* 此处省略上述已有代码 */}
        <List
          style={{marginTop: '10px', width: '300px'}}
          bordered
          dataSource={this.state.list}
          renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>}
        />
      </div>
    )
  }
  // 此处省略上述已有代码
  handleItemDelete(index) {
    const action = {
      type: 'delete_todo_item',
      index
    }
    store.dispatch(action);
  }
}
export default TodoList;
复制代码


2、在reducer中进行数据通信

接着,我们在 reducer.js 文件中,对数据进行通信。具体代码如下:

const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};
// reducer 可以接收state,但是绝不能修改state
const reducer = (state = defaultStore, action) => {
  // 此处省略上述已有代码
  if (action.type === 'delete_todo_item') {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;
复制代码

现在,我们来看下浏览器的显示效果:

2.png


👓五、逻辑归纳


1、ActionTypes的拆分

在上面的 TodoList.js 中,大家可以看到,我们会频繁地去操作 action 。同时,假设说其中的 type 如果我们稍微写错了一个字母,那排错的过程总是不好定位的。

因此,我们要来做的一件事情就是 ActionTypes 的拆分。

首先,我们在 store 文件夹下新增一个文件,命名为 actionTypes.js具体代码如下:

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
复制代码

其次,改造 TodoList.js 下的内容。具体代码如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM
} from './store/actionTypes'
class TodoList extends Component {
  handleInputChange(e) {
    const action = {
      type: CHANGE_INPUT_VALUE,
      value: e.target.value
    }
    store.dispatch(action)
  }
  handleStoreChange() {
    this.setState(store.getState())
  }
  handleBtnClick() {
    const action = {
      type: ADD_TODO_ITEM
    }
    store.dispatch(action)
  } 
  handleItemDelete(index) {
    const action = {
      type: DELETE_TODO_ITEM,
      index
    }
    store.dispatch(action);
  }
}
export default TodoList;
复制代码

最后,改造 reducer.js 文件。具体代码如下:

import {
  CHANGE_INPUT_VALUE,
  ADD_TODO_ITEM,
  DELETE_TODO_ITEM
} from './actionTypes';
const defaultStore = {
  inputValue: '123',
  list: [1, 2]
};
const reducer = (state = defaultStore, action) => {
  if (action.type === CHANGE_INPUT_VALUE) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.inputValue = action.value;
    return newState;
  }
  if (action.type === ADD_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.push(newState.inputValue);
    newState.inputValue = '';
    console.log(newState);
    return newState;
  }
  if (action.type === DELETE_TODO_ITEM) {
    const newState = JSON.parse(JSON.stringify(state));
    newState.list.splice(action.index, 1);
    return newState;
  }
  return state;
}
export default reducer;
复制代码

通过将 change_input_valueadd_todo_itemdelete_todo_item 进行整合,将其整合到 actionTypes.js 文件下,这样,如果我们遇到字母写错的情况下,也能够更好的进行排错。


2、使用actionCreator统一创建action

在上面的 TodoList.js 中,大家可以看到,对于几个绑定的事件来说,我们总是要频繁的去创建 action ,重复性地操作是在程序中最忌讳的一个事情。因此呢,我们要使用 actionCreator ,来对 action 进行统一管理,使得逻辑更加地统一完整。

首先,我们在 store 文件夹下新创建一个文件,命名为 actionCreators.js具体代码如下:

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from "./actionTypes";
export const getInputChangeAction = (value) => ({
  type: CHANGE_INPUT_VALUE,
  value: value
});
export const getAddItemAction = (value) => ({
  type: ADD_TODO_ITEM
});
export const getDeleteItemAction = (index) => ({
  type: DELETE_TODO_ITEM,
  index: index
});
复制代码

继续,我们来改造 TodoList.js具体代码如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
class TodoList extends Component {
  handleInputChange(e) {
    const action = getInputChangeAction(e.target.value);
    store.dispatch(action)
  }
  handleBtnClick() {
    const action = getAddItemAction();
    store.dispatch(action)
  } 
  handleItemDelete(index) {
    const action = getDeleteItemAction(index);
    store.dispatch(action);
  }
}
export default TodoList;
复制代码

通过将 action 中的操作统一抽离到 actionCreators.js 当中,使得最终的逻辑更加的统一。


👔六、Redux的一些总结


讲到这里,我们对上面的一些知识点进行归纳总结,具体如下


1、Redux设计和使用的三项原则

Redux 的设计和使用遵循以下三大原则:

  • store 必须是唯一的👉即整个应用之中必须且只能有一个 store
  • 只有 store 能够改变自己的内容👉即 store 不是 reducer 去更新的,而是 store 在拿到 reducer 的数据之后,自己对自己的数据进行一次更新;因此,我们回到上面的 reducer.js 文件,在 react 中,是不允许 state.inputValue === 某个值 之类的事情发生的哦,也就是说不能对其直接进行赋值。
  • Reducer 必须是纯函数👉所谓纯函数,即给定固定的输入,就一定有固定的输出,而且不会产生任何的副作用。回到我们上面的 reducer.js 文件,大家可以看到, state 是固定的, action 也是固定的,那么最终返回的 newState 自然也就是固定的。


2、Redux的核心API

我们再来复习 Redux 的几个核心 API先看下图:

3.png

现在来回顾下这几个核心 API 的作用。具体如下:

  • createStore —— 可以帮助我们创建一个 store
  • store.dispatch —— dispatch 方法帮助我们派发 action ,同时,这个 action 会传递给 store
  • store.getState —— getState 方法帮助我们获取到所有的数据;
  • store.subscribe —— subscribe 帮助我们订阅 store 的改变,只要 store 发生改变,store.subscribe 接收的回调函数就会被执行。
相关文章
|
3月前
|
API
Vue3进阶5个小知识点 附带源码
Vue3进阶5个小知识点 附带源码
44 0
|
8月前
|
编译器 Linux C语言
【C++】C++入门详解 I【C++入门 这一篇文章就够了】(上)
【C++】C++入门详解 I【C++入门 这一篇文章就够了】
48 1
|
8月前
|
编译器 Linux C语言
【C++】C++入门详解 I【C++入门 这一篇文章就够了】(下)
【C++】C++入门详解 I【C++入门 这一篇文章就够了】
51 0
|
8月前
|
Java 编译器 测试技术
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(上)
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】
46 0
|
8月前
|
存储 安全 编译器
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(下)
【C++】C++入门详解 II【深入浅出 C++入门 这一篇文章就够了】(上)
80 0
|
存储 前端开发 JavaScript
新手也可以读懂的 React18 源码分析
打造全网最简单,新手也可以看懂的 React 18 源码分析系列。共同学习 React 设计思想,提升编码能力,轻松应对前端面试
220 0
新手也可以读懂的 React18 源码分析
|
JavaScript 前端开发 中间件
【react入门手册】学习react-redux,看这篇文章就够啦!
【react入门手册】学习react-redux,看这篇文章就够啦!
114 0
|
存储 缓存 JavaScript
最新Vue常见基础面试题(看这一篇就够了)(二)
最新Vue常见基础面试题(看这一篇就够了)(二)
139 0
最新Vue常见基础面试题(看这一篇就够了)(二)
|
存储 缓存 JavaScript
我学会了,react上手知识点(下篇)
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如antd、transition、redux、react-redux、middleware、reducer、router、hooks等等。
201 0
我学会了,react上手知识点(下篇)
|
前端开发 JavaScript 中间件
我学会了,react上手知识点(上篇)
本篇文章记录的是上手react并做好一个React项目的大概知识点,比如jsx本质、生命周期、组件嵌套、父子通信、组件通信、插槽机制、跨组件通信、setState、react性能优化、ref、受控和非受控组件、高阶组件、React中使用样式。
131 0
我学会了,react上手知识点(上篇)