Redux进阶(1)UI组件、容器组件、无状态组件

UI组件和容器组件

  • 我们让 UI组件 负责页面的渲染,让 容器组件 负责页面的逻辑
  • 当然有时候让 UI组件 负责一点点逻辑也是可以的。
  • 当 UI组件 只负责页面渲染时,它实际只有一个render函数,那么此时我们可以使用无状态组件代替它以提高性能。

分离UI组件TodoListUI

在src下新建TodoListUI.js文件,将**TodoList.js的render函数内页面渲染相关的内容都剪切过去,放在组件TodoListUI中。将父组件TodoList.js中有关页面渲染的引用**都剪切到子组件TodoListUI.js中进行引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React, { Component } from 'react'//imcr
import { Input, Button, List } from 'antd';

class TodoListUI extends Component{
render(){
return(
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
<div>
<Input
value={this.props.inputValue}
placeholder="Todo info"
style={{ width: "300px", marginRight: "10px" }}
onChange={this.props.handleInputChange}
/>
<Button type="primary" onClick={this.props.handleButtonClick}>提交</Button>
</div>
<List
style={{ marginTop: "10px", width: "300px" }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => <List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>}
/>
</div >
)
}
}

export default TodoListUI;

父组件TodoList.js 的render函数中调用 子组件TodoListUI ,并将子组件需要的**state相关数据和父组件方法都通过子组件属性**的方式传递过去 ,页面的渲染就交给子组件:
构造函数中:this.handleItemDelete = this.handleItemDelete.bind(this);

1
2
3
4
5
6
7
8
9
10
11
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleButtonClick={this.handleButtonClick}
handleItemDelete={this.handleItemDelete}
/>
)
}

注意:子组件事件绑定函数传参

原本父组件中有一个事件绑定函数handleItemDelete在bind()绑定this指向的同时进行了传参,现在改为在父组件中bind()绑定this指向,在子组件中传参:

原本的:
在React这个例子中使用bind绑定事件函数 handleItemDelete 的this指向时还要将参数index传入函数,那么我们可以在绑定时传入:

1
<List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>

现在
但如果事件函数和bind绑定都在父组件中,而函数的调用和传参在子组件,就可以这样传参:

  1. 父组件的构造函数中使用bind绑定this.handleItemDelete=this.handleItemDelete.bind();
  2. 我们知道bind()返回的还是一个函数,所以在子组件中,我们通过箭头函数在每次调用handleItemDelete()时传入参数index:
    1
    <List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>
    注意
  3. 在这里不能直接使用handleItemDelete(index)传参是因为handleItemDelete(index)直接执行了函数,而onClick需要绑定的事件函数是不能立即执行的,所以需要新建一个箭头函数让他不立即执行。
  4. **箭头函数是JS表达式,在JSX中需要使用{}**,调用的父组件的函数也是JS表达式,同样中需要使用{}

容器组件(父组件TodoList

那么分离以后的父组件TodoList也就成为了容器组件


无状态组件(性能优化)

  • 当一个组件中只有render函数时,我们可以使用 无状态组件 替换 它。
  • 无状态组件:使用常量const定义一个箭头函数,该函数接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容(记得输出)

好处

相对普通组件,无状态组件性能高
因为无状态组件实际上只是一个函数,而普通组件是JS的class(类),类生成的对象里还会有一些 生命周期函数,所以普通组件既要执行生命周期函数又要执行render函数,性能就比不上 无状态组件。

例子:TodoListUI组件

TodoListUI组件替换成TodoListUI组件:
使用常量const定义无状态组件,它是一个函数,接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容。(此时原本使用this.props.xx来调用的父组件变量/方法 改为 使用props.xx来调用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from 'react'//imcr
import { Input, Button, List } from 'antd';

const TodoListUI = (props) => {
return (
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
<div>
<Input
value={props.inputValue}
placeholder="Todo info"
style={{ width: "300px", marginRight: "10px" }}
onChange={props.handleInputChange}
/>
<Button type="primary" onClick={ props.handleButtonClick}>提交</Button>
</div>
<List
style={{ marginTop: "10px", width: "300px" }}
bordered
dataSource={ props.list}
renderItem={(item, index) => <List.Item onClick={() => { props.handleItemDelete(index) }}>{item}</List.Item>}
/>
</div >
)
}

export default TodoListUI;

复习:第三方模块axios(发送AJAX)

React高级内容(3)中我们提到,React并不像jquery那样封装了AJAX发送的内置功能,所以我们需要借助 第三方模块axios来使用AJAX


Redux 中发送异步请求获取数据

  • 实现功能:我们希望发送一个AJAX请求获取数据放入列表中。
  • 其实在React高级内容(3)中我们实现过同样的功能,唯一的不同是现在我们通过reduxstore管理state数据,所以我们需要走redux的工作流程进行数据修改

总流程

先“从接口获取到数据的流程”,然后将获取到的数据通过“redux工作流程”显示在页面上。

从接口获取到数据的流程

  1. 在组件中创建一个生命周期函数componentDidMount()。引用 axios 模块,在componentDidMount函数中借助 axios 模块发送ajax请求,使用axios.get("接口路径")来获取某个接口路径下的数据,请求成功后执行then()的回调函数(接收一个参数res表示接收到的数据),请求失败后执行catch()的回调函数
  2. 桌面创建list.json文件,并在其中放入一个数组
  3. 使用接口地址在**Charles中模拟接口数据,使得发送请求到接口路径时调用我们桌面的list.json文件【使用Charles抓取localhost包需要注意更换url:需要使用http://localhost.charlesproxy.com:3000/访问**】
  4. then()的回调函数中打印出的res中的内容,可得到 res中的data是我们需要的数组,那么我们可以ajax请求成功时通过then()获取data数据

redux工作流程

  1. actionTypes.js中创建type,然后在actionCreator.js中,创建action方法
  2. 在组件TodoList.js中,引用该方法**获取action,并将从接口获取到的数据data传入action**,并通过store.dispatch(action)action发送给store
  3. store自动actionstate传给reducer.js
  4. reducer.js根据actiontype处理相关action返回newStatestore
  5. store拿到newState自动将其替换掉自己原本的state
  6. 组件通过store.subscirbe(this.handleStoreChange)监听到store的数据变化时执行回调函数this.handleStoreChange进行自身state数据的更新页面重新渲染

实例

1.在组件中创建一个生命周期函数componentDidMount()。引用 axios 模块,在componentDidMount函数中借助 axios 模块发送ajax请求,使用axios.get("接口路径")来获取某个接口路径下的数据,请求成功后执行then()的回调函数(接收一个参数res表示接收到的数据),请求失败后执行catch()的回调函数:

1
import axios from "axios";
1
2
3
4
5
6
7
8
9
10
// 生命周期函数
componentDidMount() {
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
axios.get("/list.json").then((res) => {
console.log(res);
alert("成功");
}).catch(() => {
alert("失败");
})
}

2.桌面创建list.json文件,并在其中放入一个数组:

1
["你好呀","可以通过点击删除我","今天也要加油哦"]

3.使用接口地址在**Charles中模拟接口数据,使得发送请求到接口路径时调用我们桌面的list.json文件【使用Charles抓取localhost包需要注意更换url】,使用http://localhost.charlesproxy.com:3000/访问**:
获得接口地址
`Charles`模拟接口数据
成功
获取到数据

4.then()的回调函数中打印出的res中的内容,可得到 res中的data是我们需要的数组,那么我们可以ajax请求成功时通过then()获取data数据
`res`中的`data`是我们需要的数组

1
2
3
4
5
6
7
8
9
// 生命周期函数
componentDidMount() {
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
axios.get("/list.json").then((res) => {
const data = res.data;
}).catch(() => {
alert("失败");
})
}

5.在actionTypes.js中创建type,然后在actionCreator.js中,创建action方法

1
export const INIT_LIST_ACTION="init_list_action";
1
2
3
4
export const initListAction = (data) => ({
type: INIT_LIST_ACTION,
data
})

6.在组件TodoList.js中,引用该方法**获取action,并将从接口获取到的数据data传入action**,并通过store.dispatch(action)action发送给store:

1
2
3
4
5
6
7
8
9
10
11
// 生命周期函数
componentDidMount() {
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
axios.get("/list.json").then((res) => {
const data = res.data;
const action=initListAction(data);
store.dispatch(action);
}).catch(() => {
alert("失败");
})
}

7.store自动actionstate传给reducer.js
8.reducer.js根据actiontype处理相关action返回newStatestore:

1
2
3
4
5
if (action.type === INIT_LIST_ACTION) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState;
}

【reducer中list没拿到数据】

9.store拿到newState自动将其替换掉自己原本的state
10.组件通过store.subscirbe(this.handleStoreChange)监听到store的数据变化时执行回调函数this.handleStoreChange进行自身state数据的更新页面重新渲染:
成功


,