最近在学习Hook, 了解Hook的一些特性后,希望通过一些小的demo来进行练习和巩固知识点, 达到学以致用. 本文是自己记录学习React Hook的实战练习, 同时,也是记录学习的过程, 方便日后的学习与思考 环境: react 16.8.6 , axios, antd 3.10.7
Hook在中文的意思是钩子, 而在react也是充当这个角色, Hook是服务于函数组件的方法, Hook提供了各种API, 如State Hook提供类型setState的功能, Effect Hook提供处理副作用的函数(数据订阅, 更新dom等), 也能够自定义Hook Api, 使得开发起来具有灵活性, 更多Api可以点击详情
import React, { useState } from 'react';
import { Form, Input, Table } from 'antd';
import axios from 'axios';
const container = {
margin: '10px',
padding: '6px',
};
const baseUrl = 'https://www.easy-mock.com/mock/5ce841273b839728ab376e72/api'; //mock数据的地址
const marginStyle = { margin: '6px 0px' };
const columns = [
{
title: '名字',
dataIndex: 'name',
align: 'center',
},
{
title: '年龄',
dataIndex: 'age',
align: 'center',
},
{
title: '地址',
dataIndex: 'address',
align: 'center',
},
];
function App(props) {
const [data, setData] = useState([]);
const { getFieldDecorator } = props.form;
return (
<div style={container}>
<Form layout="inline">
<Form.Item label="名字">
{
getFieldDecorator('name', {
initialValue: ''
})(
<Input />
)
}
</Form.Item>
</Form>
<Table
columns={columns}
dataSource={data}
bordered
style={marginStyle}
/>
</div>
);
}
export default Form.create()(App);
App组件是一个表格展示组件, 状态和状态的更新通过Hook中的useState. 这里的初始的data为空数组, 目前还没有人为设置数据.
import React, { useState, useEffect } from 'react';
import { Form, Input, Table } from 'antd';
import axios from 'axios';
...
function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(`${baseUrl}/getPersonList`);
setData(result.data.data.list);
};
fetchData();
});
const { getFieldDecorator } = props.form;
...
}
名为useEffect的钩子通过axios获取远程mock数据, 并且使用setData更新页面.但是在运行程序的时候, 会出现一个问题即会发送两次请求,使用useEffect发送请求时,相当于在componentDidMount和componentDidUpdate中都发送了一次请求,这显然是错误的. 应该如何避免, 并且做到在组件安装时获取数据.
import React, { useState, useEffect } from 'react';
import { Form, Input, Table } from 'antd';
import axios from 'axios';
...
function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(`${baseUrl}/getPersonList`);
setData(result.data.data.list);
};
fetchData();
},[]);
...
}
只需要在useEffect函数中, 第二个参数为空数组, 就能实现只在组件安装时获取数据. useEffect的第二个参数可用于定义函数所依赖的所有变量(在此数组中分配), 如果其中一个变量发生变化, 则uesEffect会再次执行. 如果包含变量的数组为空,则在更新组件时挂钩不会运行,因为它不必监视任何变量.更多关于Effect Hook的详情,点击此处
此时, 组件安装成功后会获取数据, 现在, 我们希望可以有个点击按钮可以触发, 重新从远端获取数据, 该如何实现
import React, { useState, useEffect } from 'react';
import { Button, Form, Input, Table } from 'antd';
import axios from 'axios';
...
function App(props) {
const [data, setData] = useState([]);
const [search, setSearch] = useState({ name: '' });
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
const result = await axios(`${baseUrl}/getPersonList`);
if (result.data.code === 0) {
setData(result.data.data.list);
setLoading(false);
} else {
setLoading(false);
}
};
fetchData();
},[search]);
const onSearch = () => {
const value = props.form.getFieldValue('name');
setSearch({ name: value });
}
const { getFieldDecorator } = props.form;
return (
<div style={container}>
<Form layout="inline">
<Form.Item label="名字">
{
getFieldDecorator('name', {
initialValue: search.name
})(
<Input />
)
}
</Form.Item>
<Form.Item>
<Button type="primary" onClick={onSearch}>查询</Button>
</Form.Item>
</Form>
<Table
columns={columns}
dataSource={data}
bordered
style={marginStyle}
loading={loading}
/>
</div>
);
}
export default Form.create()(App);
我们添加了search来管理查询的字段, 通过onSearch触发点击事件, 当search发生改变的时候, useEffect的中的fetchData会再次被触发, 从而实现手动触发数据订阅的效果. 这也就是使用Effect Hook来获取数据的方式, 关键在useEffect的第二个参数所依赖的项, 当依赖的项发生改变时, 第一个参数的内的函数也会被再次触发, 如果没用发生改变, 则不会再次执行, 这也是Effect Hook上性能优化的特点.
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中, 用于可复用的组件函数.
...
const useFetchData = () => {
const [search, setSearch] = useState({ name: '123' });
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
const result = await axios(`${baseUrl}/getPersonList`);
if (result.data.code === 0) {
setData(result.data.data.list);
setLoading(false);
} else {
setLoading(false);
}
};
fetchData();
}, [search]);
return [{ search, data, loading }, setSearch];
}
function Index(props) {
const [{ search, data, loading }, setSearch] = useFetchData();
...
}
useFetchData
则是自定义的Hook函数, 这个函数将能够获取数据相关的内容封装一个以use命名开头的函数, 并且返回一个组件所需要的数据和更新数据的方法. 而使用自定义Hook的好处, 就说组件本身不需要对于Hook有太多的了解, 只需要获取一个组件所需要的变量就可以.
到目前为止,我们已经使用各种状态挂钩来管理数据,加载状态的数据获取状态。然而,所有这些状态,由他们自己的状态钩子管理,属于一起,因为他们关心相同的数据。那让我们尝试所有与Reducer Hook结合起来.
Reducer Hook返回一个状态对象和一个改变状态对象的函数. 该函数被采用具有传递action(包含type和payload)的形式进行操作.
import React, { useState, useEffect, useReducer } from 'react';
import { Button, Form, Input, Table } from 'antd';
import axios from 'axios';
const dataFetchReducer = (state, action) => {
...
};
const initialValue = {
data: [],
loading: false,
};
const useFetchData = () => {
const [search, setSearch] = useState({ name: '123' });
const [state, dispatch] = useReducer(dataFetchReducer, initialValue);
...
}
Reducer Hook将dataFetchReducer函数和initialValue初始状态对象作为参数. 例子中, 获取的数据和loading状态没有发生改变, 不过都聚合到了reducer中, 又Reducer Hook集中管理.
const dataFetchReducer = (state, action) => {
...
};
const useFetchData = () => {
const [search, setSearch] = useState({ name: '123' });
const [state, dispatch] = useReducer(dataFetchReducer, initialValue);
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
const result = await axios(`${baseUrl}/getPersonList?search=${search}`);
if (result.data.code === 0) {
dispatch({ type: 'FETCH_SUCCESS', payload: result.data.data.list });
} else {
dispatch({ type: 'FETCH_FAILURE' });
}
};
fetchData();
}, [search]);
return [search, setSearch, state];
}
现在,在获取数据时,可以通过dispatch函数将数据发生发送到reducer功能上.而在自定义的Hook中返回的对应的状态. 接下来,需要实现dataFetchReducer函数了
...
const dataFetchReducer = (state, action) => {
switch(action.type) {
case 'FETCH_INIT':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return { ...state, data: action.payload, loading: false };
case 'FETCH_FAILURE':
return { ...state, loading: false };
default:
throw new Error();
}
};
...
现在,由动作类型决定的每个状态转换都会返回基于先前状态和可选有效负载的新状态。例如,在成功请求的情况下,有效载荷用于设置新状态对象的数据。
总之,Reducer Hook确保状态管理的这一部分用自己的逻辑封装。通过提供操作类型和可选的有效负载,你将可以以自己可预见的状态结束。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。