转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥"前端一万小时"两大明星专栏------"从零基础到轻松就业"、"前端面试刷题",已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
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 拿到了 previousValue
和 action
,接下来 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 拿到了 previousValue
和 action
,接下来 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 拿到了 previousValue
和 action
,接下来 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!