(22)Redux 入门——④ Action 和 Reducer 的编写 | React 基础理论实操

转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

javascript 复制代码
涉及面试题
1. 我可以在 reducer 中触发一个 Action 吗?
2. 如何在加载时触发 Action?
3. 为什么 Redux 状态函数称为 reducers?
4. Redux DevTools 的功能有哪些?

编号:[react_22]

1 安装 Redux Devtools

1️⃣在开始本篇之前,我们先去下载并安装一个 Chrome 的插件------Redux Devtools,以帮助我们进行 Redux 项目的开发和调试:

2️⃣插件安装完成后,打开页面查看(页面提示:找不到 store,没法做调试):

3️⃣点击 the instructions ,跟着指南进行配置(复制红色小框框住的部分):

4️⃣将其粘贴至 index.js 中:

javascript 复制代码
import { createStore } from "redux"; 

import reducer from "./reducer"; 

const store = createStore(
  reducer,
  
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); /*
	 🚀粘贴过来的这行代码表示:如果 window 下边有 __REDUX_DEVTOOLS_EXTENSION__ 这个变量的话,
	 就执行这个变量对应的方法 window.__REDUX_DEVTOOLS_EXTENSION__()。
    
   即,若安装了这个工具,就在页面去使用这个工具!
    */

export default store; 

5️⃣再次在页面查看(很多数据信息都一目了然了,接下来再做更复杂的 Redux 调试就很方便了):

2 实现"输入框"的交互

接下来,我们要实现:在页面 input 框输入内容的同时,内容会展示在 input 框里(即,input 框里的内容发生改变时,数据项 inputValue 也跟着变)。

我们结合之前的"流程图",用代码来一行行实现功能。

1️⃣首先,完成"我要借 xx 书 "这个流程。

在组件 TodoList 中创建一个 action:

jsx 复制代码
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); /*
    																														1️⃣-②:一定得记得去
																																改变 this 的指向;
                                                                 */
  }
  
  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}  
          /> {/* 1️⃣-①:给 input 绑定一个事件; */}
          
          <Button type="primary">提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.state.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />
          

        </div>
      </div>
    )
  }
  
  handleInputChange(e) { // 1️⃣-③:在这里写方法的具体逻辑(❗️注意要接收 e 这个事件);
  
    /*
    1️⃣-④:接下来,我要创建一个 action,让它表示出我要做的事。
    ❗️❗️❗️在 React 里,action 需要用"对象"的形式来表示:
    里边有一个 type 属性,用它来告诉 store"你要帮我做的事情是什么?";
     */
    const action = {
      type: "change_input_value", // 1️⃣-⑤:你要帮我"改变 input 框的 value 值";
      
      value: e.target.value // 1️⃣-⑥:这个值应该是 e.target.value。
    }
  }
}

export default TodoList;

2️⃣接着,将"我要借 xx 书"这句话传给 store:

jsx 复制代码
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);  
  }
  
  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">提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.state.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />
          

        </div>
      </div>
    )
  }
  
  handleInputChange(e) { 
    const action = {
      type: "change_input_value", 
      
      value: e.target.value  
    }
    
    store.dispatch(action); /*
    												🚀调用 store 提供的 dispatch 方法,
    											  将这句话传给 store。
                             */
  }
}

export default TodoList;

3️⃣store 虽然知道了你想做的事,但它不知道应该怎么处理这个数据,它需要去查看"记录本"。

即,store 会拿着"当前 store 里的数据 previousState "和"接收到的 action "去查看"记录本"。

"记录本"reducer 会告诉 store 你要做什么。

幸运的是,Redux 中的 store 会自动 地帮我们把 "当前 store 里的数据 previousState "和"接收到的 action " 转发给 reducer。

打开 reducer.js 文件,验证一下:

javascript 复制代码
const defaultState = { 
  inputValue: "Hello, Oli.", 
  list: [1,2,3]
};

export default (state = defaultState, action) => { /*
																								3️⃣-②:上一篇文章遗留问题。
																								state 指:store 里边上一次存储的数据(
                                                previousState);
                                                action 指:传过来的那句话。
                                                    */
  
  console.log(state, action); /*
  														🚀3️⃣-①:为了验证 reducer 是否接收到 store 
  														转发过来的 previousState 和 action,
                              我们在控制台打印一下看看。
                               */
  
  return state;
}

返回页面控制台查看(reducer 确实接收到了 store 自动转发过来的相关信息):

4️⃣既然 reducer 拿到了 previousValueaction ,接下来 reducer 就需要告诉 store,现在你"新的数据"应该变成什么样子。

打开 reducer.js 文件:

javascript 复制代码
const defaultState = { 
  inputValue: "Hello, Oli.", 
  list: [1,2,3]
};

export default (state = defaultState, action) => { 
  
  if(action.type === "change_input_value") { /*
  																					 4️⃣-①:如果 action 的类型是 
                                             change_input_value,就执行下边的代码;
                                              */
    
    const newState = JSON.parse(JSON.stringify(state)); /*
    																			4️⃣-②:先对之前的 state 
    																			作一个"深拷贝",并赋值给 newState;
                                          ❗️❗️❗️之所以这样做,是因为:
                                          reducer 可以接收 state,但绝不能修改 state!
                                                         */
  
    newState.inputValue = action.value; /*
    																		4️⃣-③:然后,使"新数据"的 inputValue 值等于
                                        "转发"过来的 value 值。
                                         */
    
    return newState; /*
    								 4️⃣-④:将最新的 newState 返回出来,并给到 store。
    									
                     ❗️❗️❗️即,整个流程为:store 将接收到的"你想做的事"和"当前的数据"
                     都转发给 reducer 去处理。
                     reducer 处理完成后,返回一个 newState 给 store。
                     然后 store 拿着这个"新数据"自动去替换掉 store 里的"老数据"就可以了。
    									*/
  }
  
  console.log(state, action);  
  return state;
}

返回页面,用 redux-devtools 工具查看一下数据是否变化(确实变化了):

5️⃣数据虽然变化了(store 已经变化了),但页面还没显示出来,故我们需要在"组件"TodoList 里进行一些操作:

jsx 复制代码
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); /*
    																														5️⃣-③:注意修改
    																														this 指向;
                                                                 */
    
    /*
    🚀5️⃣-①:此处,显式地让 TodoList "订阅"store。
    即,一旦 store 里的数据发生改变,subscribe 里的函数就会自动执行!
     */
    store.subscribe(this.handleStoreChange); /*
    																				 5️⃣-②:本例中,一旦 store 中数据改变,
                                             就执行 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">提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.state.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />
          

        </div>
      </div>
    )
  }
  
  handleInputChange(e) { 
    const action = {
      type: "change_input_value", 
      
      value: e.target.value  
    }
    
    store.dispatch(action);  
  }

  handleStoreChange() { // 5️⃣-④:在这里定义函数的逻辑;
    
    this.setState(store.getState()); /*
    																 5️⃣-⑤:当 TodoList 组件感知到 store 
                                     中的数据发生变化时,TodoList 就调用 
                                     store.getState 方法,重新从 store 
                                     中取一次数据;
                                     然后调用 setState 替换掉当前组件里的数据。
                                     🏆如此一来,"组件"里的数据就和 store 里的数据同步了。
                                      */
  }
}

export default TodoList;

查看下页面效果:

3 实现"提交"按钮的交互

同理(我们再过一次"流程图",多过几次就会烂熟于心的),接着实现:点击"提交"按钮后,输入框中的内容会相应地显示在"列表项"里。

打开 TodoList.js 文件:

jsx 复制代码
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.handleButtonClick = this.handleButtonClick.bind(this); /*
    																														2️⃣记得改变
    																														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}  
          /> 
          
          {/* 1️⃣首先,给"按钮"绑定一个点击事件; */}
          <Button type="primary" onClick={this.handleButtonClick}>提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.state.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />
          

        </div>
      </div>
    )
  }
  
  handleInputChange(e) { 
    const action = {
      type: "change_input_value", 
      
      value: e.target.value  
    }
    
    store.dispatch(action);  
  }

  handleStoreChange() { 
    
    this.setState(store.getState()); 
  }

	handleButtonClick() { // 3️⃣在这里编写事件的逻辑;
    
    const action = { // 4️⃣创建一个 action,记得这个 action 是一个"对象"的形式;
      
      type: "add_todo_item" // 5️⃣action 里边需要有一个 type 属性,用来告诉 store "要干什么";
    };
    
    store.dispatch(action); // 6️⃣利用 store 提供的 dispatch 方法,将 action 传递给 store;
  }

}

export default TodoList;

7️⃣store 会拿着"当前 store 里的数据 previousState "和"接收到的 action "去查看"记录本"。"记录本"reducer 会告诉 store 你要做什么。

幸运的是,Redux 中的 store 会自动 地帮我们把 "当前 store 里的数据 previousState "和"接收到的 action " 转发给 reducer。

8️⃣既然 reducer 拿到了 previousValueaction ,接下来 reducer 就需要告诉 store,现在你"新的数据"应该变成什么样子。

🔗前置知识:《JavaScript 基础------JS 数组:① ES3 数组方法》

打开 reducer.js 文件:

javascript 复制代码
const defaultState = { 
  inputValue: "Hello, Oli.", 
  list: [1,2,3]
};

export default (state = defaultState, 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") { /*
  																			8️⃣-①:如果 action 的类型是 add_todo_item,
  																			就执行以下代码;
                                         */
    
    const newState = JSON.parse(JSON.stringify(state)); /*
    																			8️⃣-②:先对之前的 state 
    																			作一个"深拷贝",并赋值给 newState;
                                          ❗️❗️❗️之所以这样做,是因为:
                                          reducer 可以接收 state,但绝不能修改 state!
                                                         */
  
    newState.list.push(newState.inputValue); /*
    																				 8️⃣-③:利用 ES3 数组方法,将 newState
    																				 的 inputValue 放到数组 list 的最后一项;
                                              */
    
    newState.inputValue = ""; // 8️⃣-④:同时,清空输入框内容;
    
    return newState; // 8️⃣-⑤:❗️一定记得将"新数据"返回给 store。
  }
  
 
  return state;
}

9️⃣reducer 处理完成后,返回一个 newState 给 store。然后 store 拿着这个"新数据"自动去替换掉 store 里的"老数据"就可以了。

🔟实现页面变化,这部分逻辑代码不用重新编写,因为上边 2.5️⃣ 中已编写好了,2.5️⃣的代码如下:

jsx 复制代码
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); /*
    																														5️⃣-③:注意修改
    																														this 指向;
                                                                 */
    
    /*
    🚀5️⃣-①:此处,显式地让 TodoList "订阅"store。
    即,一旦 store 里的数据发生改变,subscribe 里的函数就会自动执行!
     */
    store.subscribe(this.handleStoreChange); /*
    																				 5️⃣-②:本例中,一旦 store 中数据改变,
                                             就执行 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">提交</Button>  

          <List style={{marginTop: "10px", width: "300px"}} 

            bordered
            dataSource={this.state.list}
            renderItem={item => <List.Item>{item}</List.Item>}
          />
          

        </div>
      </div>
    )
  }
  
  handleInputChange(e) { 
    const action = {
      type: "change_input_value", 
      
      value: e.target.value  
    }
    
    store.dispatch(action);  
  }

  handleStoreChange() { // 5️⃣-④:在这里定义函数的逻辑;
    
    this.setState(store.getState()); /*
    																 5️⃣-⑤:当 TodoList 组件感知到 store 
                                     中的数据发生变化时,TodoList 就调用 
                                     store.getState 方法,重新从 store 
                                     中取一次数据;
                                     然后调用 setState 替换掉当前组件里的数据。
                                     🏆如此一来,"组件"里的数据就和 store 里的数据同步了。
                                      */
  }
}

export default TodoList;

看看页面效果:

4 实现"删除"功能

同理,接着实现:点击"列表项"中的任意项时,该项会从列表中移除。

打开 TodoList.js 文件:

jsx 复制代码
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.handleButtonClick = this.handleButtonClick.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.handleButtonClick}>提交</Button>  

          <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>}
          />  {/*
          		 1️⃣首先,对组件的每一项进行"点击"事件的绑定;
               ❗️由于我们使用了 Antd Design,故列表项是通过 List.item 渲染的。
               ❗️renderItem 除了接收参数 item,它还可以接收一个参数 index。
               ❗️注意在用 bind 改变 this 的指向时,可以传递一个 index 参数。
                */}
          
        </div>
      </div>
    )
  }
  
  handleInputChange(e) { 
    const action = {
      type: "change_input_value", 
      
      value: e.target.value  
    }
    
    store.dispatch(action);  
  }

  handleStoreChange() { 
    
    this.setState(store.getState()); 
  }

  handleButtonClick() { 
    const action = {  
      type: "add_todo_item" 
    };
    
    store.dispatch(action); 
  }

	handleItemDelete(index) { /*
  													2️⃣在这里写方法的具体逻辑(方法接收一个 index 参数------
  													它表示"点击的每一项的索引");
                             */
    
    const action = {
      type: "delete_todo_item", // 3️⃣要做的事的类型为 delete_todo_item;
      index // 4️⃣要将"点击的每一项的索引"一并传给 reducer;
    };
    
    store.dispatch(action); // 5️⃣调用 store 的 dispatch 方法,将 action 发送给 store;
  }	

}

export default TodoList;

6️⃣store 会拿着"当前 store 里的数据 previousState "和"接收到的 action "去查看"记录本"。"记录本"reducer 会告诉 store 你要做什么。

幸运的是,Redux 中的 store 会自动 地帮我们把 "当前 store 里的数据 previousState "和"接收到的 action " 转发给 reducer。

7️⃣既然 reducer 拿到了 previousValueaction ,接下来 reducer 就需要告诉 store,现在你"新的数据"应该变成什么样子。

打开 reducer.js 文件:

javascript 复制代码
const defaultState = { 
  inputValue: "", // 7️⃣-①:将这里的数据初始化为"空";
  list: []
};

export default (state = defaultState, 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 = ""; 
    
    return newState; 
  }
  
 	if(action.type === "delete_todo_item") { /*
  																		7️⃣-②:如果 action 的类型是 delete_todo_item,
  																		就执行以下代码;
                                            */
    
    const newState = JSON.parse(JSON.stringify(state)); /*
    																			7️⃣-③:先对之前的 state 
    																			作一个"深拷贝",并赋值给 newState;
                                          ❗️❗️❗️之所以这样做,是因为:
                                          reducer 可以接收 state,但绝不能修改 state!
                                                         */
    
    newState.list.splice(action.index, 1); /*
    																			 7️⃣-④:利用 ES3 数组方法 splice,
    																			 删除点击的那一项;
                                            */
    return newState; // 7️⃣-⑤:注意把"新数据"返回给 store。
  }
  
  return state;
}

8️⃣reducer 处理完成后,返回一个 newState 给 store。然后 store 拿着这个"新数据"自动去替换掉 store 里的"老数据"就可以了。

9️⃣实现页面变化(这部分代码不用重新编写,上边 2.5️⃣ 中已编写好了,这里不再赘述)。

查看页面效果:

祝好,qdywxs ♥ you!

相关推荐
莹雨潇潇4 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr12 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
太阳花ˉ2 小时前
html+css+js实现step进度条效果
javascript·css·html
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
john_hjy3 小时前
11. 异步编程
运维·服务器·javascript
风清扬_jd3 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js