day-078-seventy-eight-20230526-TaskOA任务管理系统-其它Hook
TaskOA任务管理系统
-
对话弹框Modal。
-
在React项目中,如果遇到表单操作,如果不使用antd中的组件,自己去开发的步骤:
<div> <label>任务描述:</label> <textarea value={xxx} onChange={(ev) => { setXxx(ev.target.value.trim()); }} onBlur={() => {}} ></textarea> </div>
- 搭建结构写样式。
- 定义相关的状态,并且监听 onChange 事件,确保当表单中的内容改变,我们需要把对应的状态也更改!
- 还需要对每个表单做规则校验,包括最后提交的时候,所有的校验也要走一遍。
- 关闭Modal框的时候,还需要自己手动的清楚表单中的信息及校验信息。
- antd中提供了
<Form>组件
,可以帮助我们快速进行表单操作,类似于实现了上述4个要做的事情
。<Form>
、<Form.Item>
- 除了结构样式外。
- 组件会自动收集每一个 Item 中,表单中输入的最新内容,并且可以给每个表单设置初始值「无需我们自己定义状态及基于onChange事件去监听内容的变化,从而修改状态值」。
- 并且提供了优秀的表单校验方案。
- 提供了大量方法,有触发表单校验的方法,也有清除表单信息及校验信息的方法!
Form组件
-
<Form>
、<Form.Item>
- 除了结构样式外。
- 组件会自动收集每一个 Item 中,表单中输入的最新内容,并且可以给每个表单设置初始值「无需我们自己定义状态及基于onChange事件去监听内容的变化,从而修改状态值」。
- 并且提供了优秀的表单校验方案。
- 提供了大量方法,有触发表单校验的方法,也有清除表单信息及校验信息的方法!
-
获取Form实例的方案:
let formIns = Form.useForm() <Form colon={false} form={formIns}></Form> console.log(`确认新增任务`,formIns); // 清除表单信息及校验信息 formIns.resetFields()
-
基于组件库提供的 form / Form.useForm()。
-
调用Form.useForm()提供的hook函数。
import { Form } from 'antd' let [formIns] = Form.useForm()
-
在Form组件上的from属性上绑定。
<Form form={formIns} ></Form>
-
在函数中调用实例。
console.log(formIns)
-
-
基于ref获取。
- 获取实例后,就可基于其提供的方法,做一些事情!
-
-
<Form>
就是基于<Form.Item>每一项的name
,进行信息自动收集。- Form的配置设置1
收集的信息={ task:'...', time:'...', }
-
<Form.Item>
-
Form表单校验
- 所有需要校验的表单,都必须在
<Form.Item>
中包含「每一个Item中正出现一个表单」 - 给每个ITEM设置 name 以及 基于 rules 设置校验的规则
- 内置校验规则
- 自定义校验「或者基于正则校验」
- 触发表单校验
- 第一种: 获取到 formIns 实例,基于实例中的 validate… 方法去手动触发
- 第二种:子自动触发- 例如:
- 提交按钮的时候,自动触发
- 按钮必须在
<Form>
中 - 按钮的原生 type 类型必须是 submit
- 只有这样,点击按钮的时候,才会自动触发
Form表单
校验- 成功:onFinish「参数中包含了Form收集的各个表单信息」
- 失败:onFinishFailed
- 按钮必须在
- 提交按钮的时候,自动触发
- 例如:
- 所有需要校验的表单,都必须在
-
校验规则:
-
枚举:
rules={[ { type: "enum", enum: ["A", "B"], message: "不符合规则", }, ]}
-
自定义规则:
rules={[ { validator(_, value) { value = value.trim(); if (value.length === 0) return Promise.reject("必填哈"); return Promise.resolve(); }, }, ]}
-
正则规则:
rules={[ { pattern: /^.{1,5}$/, message: "呜呜呜呜", }, ]}
-
多个规则:
rules={[ { pattern: /^.{1,5}$/, message: "呜呜呜呜", }, { required: true, message: "哈哈哈哈", }, ]}
-
-
表单相关的组件如何处理
- 结构样式如何调整
- 如何让from收集到各表单中信息
- 如何写校验规则,如果触发校验规则
- 如何拿到form实例,以及实例可以做什么
dayjs
- 时间格式化
服务端
- 如果是用node的,那么一般先看package.json中的scripts。如果没有找到,就直接用node执行server.js之类的入口文件。
服务端窗口管理
- 一般一个控制台开了,但里面的窗口可能在终端开了之后。一般用pm2来管理。
npm i pm2 -g //全局安装pm2,以便可以使用pm2命令。
pm2 start server.js --name TASK//开启一个的有名字的pm2服务。
pm2 stop TASK//停止执行一个有名字的pm2服务。
pm2 delete TASK//删除一个有名字的pm2服务。
pm2 restart TASK//重启一个有名字的pm2服务。
pm2 list //查看当前执行的pm2服务。
React的事件委托
因为React的事件绑定本身就是事件委托来做的,所以不必写成事件委托。
{["全部", "未完成", "已完成"].map((item, index) => {
return (
<Tag
key={index}
color={selectedIndex === index ? "#1677ff" : ""}
onClick={() => {
if(selectedIndex===index){
return
}
setSelectedIndex(index)
}}
>
全部
</Tag>
);
})}
axios的配置
- 基于axios 发送请求,返回结果是一个 Promise 实例。
- 对于一个网络请求,用户及需求要求的是业务层成功。
-
请求成功:
- 只是表示收到服务器返回的消息了,但服务器返回的消息会提示我们传的数据是否正确。
- 一般服务器表示是否成功,是通过一个字段code来约定的是否成功了。
- 一般到这一步,就算失败了,后端也会返回是因为什么原因导致的前后端交互失败,如给一个message字段。
- 业务层成功(例如:返回数据中的 code===0)
- 业务层失败
-
请求失败「网络层失败 状态码不是以2开始的」promise实例是失败的:
- 这一步,就是网络的问题的。如服务器着火,请求地址不存在,请求地址写错了-不是后端给的那个地址。
- 到这一步,一般是根据状态码,如404与401或101之类的,由前端大概判断是因为什么导致的。
- 实际上,这一步大概率是前端或运维的问题。比如前端代码写错了,或者是运维服务器挂了。
-
在axios二次封装的响应拦截器中,做了统一的处理!
http.interceptors.response.use(response => { return response.data }, reason => { message.error('当前网络繁忙,请您稍后再试~') return Promise.reject(reason) })
-
示例:
// 从服务器获取指定状态的任务。 const initData = async () => { setLoading(true); try { let { code, list } = await API.queryTaskList(selectedIndex); if (+code !== 0) { list = []; } setData(list); } catch (_) { console.log(_); } setLoading(false); };
-
示例意思:
// 从服务器获取指定状态的任务。 // 对于一个网络请求,用户及需求要求的是业务层成功。 const initData = async () => { //这里是请求发送前的代码。 setLoading(true); try { let { code, list } = await API.queryTaskList(selectedIndex); //到这一步,就代表axios网络层成功了。运维基本上不用理了。 //到这一步,需要前后端约定了,比如约定code等于0表示接口处理成功,即本次请求达成前端预期了。 if (+code !== 0) { //这里处理axios网络层但业务层失败的任务。 list = []; } //这里处理axios网络层并且业务层也成功的任务。 setData(list); } catch (_) { //这里处理axios网络层失败的错误。 console.log(_); } //这里是处理axios发送结束并且处理好各个请求错误之后的代码。 setLoading(false); };
-
项目代码
-
/src/api/http.js
axios实例默认配置:import axios from "axios" import qs from 'qs' import _ from '@/assets/utils' import { message } from 'antd' const http = axios.create({ baseURL: '/api', timeout: 60000 }) http.defaults.transformRequest = data => { if (_.isPlainObject(data)) data = qs.stringify(data) return data } http.interceptors.response.use(response => { return response.data }, reason => { message.error('当前网络繁忙,请您稍后再试~') return Promise.reject(reason) }) export default http
-
/src/api/index.js
使用axios实例的接口:import http from "./http"; // 获取指定状态下的任务列表 const queryTaskList = (state = 0) => { return http.get("/getTaskList", { params: { state, }, }); }; // 新增任务 const insertTaskInfo = (task, time) => { return http.post("/addTask", { task, time, }); }; // 删除任务 const removeTaskById = (id) => { return http.get("/removeTask", { params: { id, }, }); }; // 完成任务 const updateTaskById = (id) => { return http.get("/completeTask", { params: { id, }, }); }; /* 暴露API */ const API = { queryTaskList, insertTaskInfo, removeTaskById, updateTaskById, }; export default API;
-
/src/index.jsx
入口文件:import React from "react"; import ReactDOM from "react-dom/client"; /* ANTD */ import { ConfigProvider } from "antd"; import zhCN from "antd/locale/zh_CN"; import dayjs from "dayjs"; import "dayjs/locale/zh-cn"; /* 组件&样式 */ import "./index.less"; // import Task from './views/Task' import Demo from "./views/Demo1"; dayjs.locale("zh-cn"); const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <ConfigProvider locale={zhCN}> {/* <Task /> */} <Demo /> </ConfigProvider> );
-
/src/views/Task.jsx
主要的代码:import { useState, useEffect } from "react"; import styled from "styled-components"; import { Button, Tag, Table, Popconfirm, Modal, Form, Input, DatePicker, message, } from "antd"; import _ from "@/assets/utils"; import dayjs from "dayjs"; import API from "@/api"; /* 组件样式 */ const TaskStyle = styled.div` box-sizing: border-box; margin: 0 auto; width: 800px; .header-box { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #ddd; .title { font-size: 20px; font-weight: normal; } } .tag-box { margin: 15px 0; .ant-tag { padding: 5px 15px; margin-right: 15px; font-size: 14px; cursor: pointer; } } .ant-btn-link { padding: 4px 5px; } .ant-modal-header { margin-bottom: 20px; } `; /* 组件视图 */ export default function Task() { // 定义表格列 const columns = [ { title: "编号", dataIndex: "id", width: "6%", align: "center", }, { title: "任务描述", dataIndex: "task", width: "50%", ellipsis: true, }, { title: "状态", dataIndex: "state", width: "10%", align: "center", render: (text) => (+text === 1 ? "未完成" : "已完成"), }, { title: "完成时间", width: "16%", align: "center", render(text, record) { let { state, time, complete } = record; time = +state === 1 ? time : complete; return _.formatTime(time, "{1}/{2} {3}:{4}"); }, }, { title: "操作", width: "18%", render(text, record) { let { state, id } = record; return ( <> <Popconfirm title="您确定要删除此任务吗?" onConfirm={handleRemove.bind(null, id)} > <Button type="link" danger> 删除 </Button> </Popconfirm> {+state === 1 ? ( <Popconfirm title="您确定要把此任务设置为已完成吗?" onConfirm={handleUpdate.bind(null, id)} > <Button type="link">完成</Button> </Popconfirm> ) : null} </> ); }, }, ]; // 定义状态 let [data, setData] = useState([]), [loading, setLoading] = useState(false), [selectedIndex, setSelectedIndex] = useState(0); let [modalVisible, setModalVisible] = useState(false), [confirmLoading, setConfirmLoading] = useState(false); let [formIns] = Form.useForm(); // 第一次渲染完&每一次选中状态改变,都要从服务器重新获取数据 useEffect(() => { initData(); }, [selectedIndex]); // 从服务器获取指定状态的任务 const initData = async () => { setLoading(true); try { let { code, list } = await API.queryTaskList(selectedIndex); if (+code !== 0) list = []; setData(list); } catch (_) {} setLoading(false); }; // 关闭Modal对话框 const closeModal = () => { setModalVisible(false); setConfirmLoading(false); // 清除表单信息及校验信息 formIns.resetFields(); }; // 确认新增任务 const submit = async () => { try { await formIns.validateFields(); let { task, time } = formIns.getFieldsValue(); // time:是基于dayjs构建的日期对象 time = time.format("YYYY-MM-DD HH:mm:ss"); // 向服务器发送请求 setConfirmLoading(true); let { code } = await API.insertTaskInfo(task, time); if (+code === 0) { message.success("恭喜您,新增成功~"); closeModal(); // 从服务器获取最新的任务列表 initData(); } else { message.error("很遗憾,新增失败,请稍后再试~"); } } catch (_) {} setConfirmLoading(false); }; // 删除任务 const handleRemove = async (id) => { try { let { code } = await API.removeTaskById(id); if (+code === 0) { message.success("恭喜您,删除成功"); // 让客户端页面中的数据也跟着删除 // initData() let arr = data.filter((item) => +item.id !== +id); setData(arr); return; } message.error("很遗憾,删除失败,请稍后再试"); } catch (_) {} }; // 修改任务 const handleUpdate = async (id) => { try { let { code } = await API.updateTaskById(id); if (+code === 0) { message.success("恭喜您,修改成功"); // 让页面中的数据也会跟着变 let arr = data.map((item) => { if (+item.id === +id) { item.state = 2; item.complete = dayjs().format("YYYY-MM-DD HH:mm:ss"); } return item; }); setData(arr); return; } message.error("很遗憾,修改失败,请稍后再试"); } catch (_) {} }; return ( <TaskStyle> <div className="header-box"> <h2 className="title">TASK OA 任务管理系统</h2> <Button type="primary" onClick={() => setModalVisible(true)}> 新增任务 </Button> </div> <div className="tag-box"> {["全部", "未完成", "已完成"].map((item, index) => { return ( <Tag key={index} color={selectedIndex === index ? "#1677ff" : ""} onClick={() => { if (selectedIndex === index) return; setSelectedIndex(index); }} > {item} </Tag> ); })} </div> <Table columns={columns} dataSource={data} loading={loading} pagination={false} rowKey="id" size="middle" /> <Modal title="新增任务窗口" okText="确认提交" keyboard={false} maskClosable={false} getContainer={false} confirmLoading={confirmLoading} open={modalVisible} afterClose={closeModal} onCancel={closeModal} onOk={submit} > <Form colon={false} layout="vertical" validateTrigger="onBlur" form={formIns} initialValues={{ task: "", time: dayjs().add(1, "day"), }} > <Form.Item name="task" label="任务描述" rules={[{ required: true, message: "任务描述是必填项" }]} > <Input.TextArea rows={4} /> </Form.Item> <Form.Item name="time" label="预期完成时间" rules={[{ required: true, message: "请先选择预期完成时间" }]} > <DatePicker showTime /> </Form.Item> </Form> </Modal> </TaskStyle> ); }
项目总结
- 搭建结构:DOM骨架
- antd组件库的运用
- 按需加载与按需导入
- 国际化-由英文化改为中文化
- 核心组件
- Table表格组件
- 列的构建及格式化处理
- 表格的分页
- 列的选择
- 列的排序
- 列的筛选
- Form表单
- 数据的处理和收集
- 规则校验及触发机制
- 内置触发
- 通过实例触发
- 内置规则
- 自定义规则
- 实例及方法等
- Modal与Dialog对话框
- 文件上传等操作
- …
- Table表格组件
- 搭好骨架
- antd组件库的运用
- 写样式
- 样式私有化的处理方案
- UI组件库中样式的修改
- 那些提取为全局样式
- …
- 数据通信
- 跨域配置
- Axios的二次配置
- API接口的管理
- …
其它Hook
useMemo
-
假设:z的值是依赖于x状态值计算出来的,而且非常消耗性能。
import { Button } from "antd"; import { useState, useMemo } from "react"; import styled from "styled-components"; // 一点破样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 300px; border: 1px solid #ddd; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; export default function Demo() { let [x, setX] = useState(10), [y, setY] = useState(20); // 假设:z的值是依赖于x状态值计算出来的「而且非常消耗性能」 console.time("AAA"); let z = 0; new Array(9999999).fill(null).forEach(() => { // ... z += x / 10; }); console.timeEnd("AAA"); z = z.toFixed(2); return ( <DemoStyle> <p> x:{x} -- z:{z} -- y:{y} </p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改X </Button> <Button type="primary" size="small" onClick={() => setY(y + 1)}> 修改Y </Button> </DemoStyle> ); }
- 所以就想要z只有在它所依赖的x变动之后才会重新进行计算。
-
useMemo就是在React中做优化的,类似于Vue中的computed计算属性;基于useMemo可以完成:
- 组件第一次渲染,把useMemo中的callback执行,计算出一个值赋值给z。
- 并且缓存这个值。
- 以后组件的每一次更新,只有依赖的x状态发生改变,callback才会重新执行,计算出新的值;
- 如果x状态没有变化,会使用之前缓存的值!
- 组件第一次渲染,把useMemo中的callback执行,计算出一个值赋值给z。
-
useMemo就是所谓的计算缓存。
-
如果计算过程很简单,或者不需要缓存,就不必使用useMemo。
-
useMemo也比较耗性能。
-
真实项目中,如果遇到
依赖某个
或者依赖某几个状态
算出一个新的值
这样的需求,并且计算过程有一些复杂,需要消耗很多时间或者性能,此时我们基于useMemo对其进行优化处理!import { Button } from "antd"; import { useState, useMemo } from "react"; import styled from "styled-components"; // 一点破样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 300px; border: 1px solid #ddd; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; export default function Demo() { let [x, setX] = useState(10), [y, setY] = useState(20); // 假设:z的值是依赖于x状态值计算出来的「而且非常消耗性能」 // useMemo就是在React中做优化的,类似于Vue中的computed计算属性;基于useMemo可以完成: // + 组件第一次渲染,把useMemo中的callback执行,计算出一个值赋值给z「并且缓存这个值」 // + 以后组件的每一次更新,只有依赖的x状态发生改变,callback才会重新执行,计算出新的值;如果x状态没有变化,会使用之前缓存的值! // ===> 计算缓存:真实项目中,如果遇到 “依赖某个或者某几个状态,算出一个新的值” 这样的需求,并且计算的过程有一些复杂,需要消耗很多时间或者性能,此时我们基于 useMemo 对其进行优化处理! let z = useMemo(() => { console.time("AAA"); let z = 0; new Array(9999999).fill(null).forEach(() => { // ... z += x / 10; }); console.timeEnd("AAA"); return z.toFixed(2); }, [x]); return ( <DemoStyle> <p> x:{x} -- z:{z} -- y:{y} </p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改X </Button> <Button type="primary" size="small" onClick={() => setY(y + 1)}> 修改Y </Button> </DemoStyle> ); }
-
useCallback
父子组件处理流程
-
父子组件处理流程:
- 父组件第一次渲染:
- 父组件渲染之前 --> 父组件渲染 --> 遇到子组件 -->
- –> 子组件渲染之前 --> 子组件渲染 --> 子组件渲染完毕 -->
- –> 父组件结束子组件的渲染 --> 父组件渲染完毕。
- 父组件后续更新:
- 父组件更新之前 --> 父组件更新 --> 遇到子组件 -->
- –> 子组件更新之前 --> 子组件更新 --> 子组件更新完毕 -->
- –> 父组件结束子组件的更新 --> 父组件更新完毕。
- 和vue组件父子组件的更新
- 处理的原则是:深度优先原则,只有把子组件先处理完毕,才能继续处理父组件。
- 父组件第一次渲染,子组件也是第一次渲染。
- 父组件进行更新,子组件也是进行更新。
- 父组件第一次渲染:
-
会发现父组件更新后,子组件必定也更新了。
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends Component { render() { console.log(`子组件渲染与更新`); return <div>子组件</div>; } } export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); return ( <DemoStyle className="demo-box"> <Child></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
-
会发现父组件如果更新,子组件却不更新。
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends PureComponent { render() { console.log(`子组件渲染与更新`); return <div>子组件</div>; } } export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); return ( <DemoStyle className="demo-box"> <Child></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
- 原因是因为子组件继承自React.PureComponent,React.PureComponent默认会比对前后的状态及父组件传递的props值。而由于父组件前后都没有传递props,而子组件的前后状态也都没变化,所以子组件不会重新生成虚拟DOM,进而不更新。
useCallback()钩子函数
useCallback()
-
父组件每一次更新,都是把函数重新执行,产生一个新的闭包;闭包中的handle方法也会重新创建一个新的函数(新的堆内存);这样每一次传递给 Child 的属性值,都是新的堆内存地址;所以不论子组件继承的是 PureComponent 还是 Component ,每一次更新,其接收的属性值都是发生变化的,所以子组件也要更新!
-
但是函数组件本应如此,只有每一次更新都创建新的函数出来,这样在函数中用的状态,才是当前闭包中的信息!
-
如果每次更新,handle方法依然是初次渲染闭包一中的handle,那么在handle中,我们使用的状态,也依然是闭包一中的!
import { Button } from "antd"; import { useState, useCallback, PureComponent, memo } from "react"; import styled from "styled-components"; // 一点破样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 300px; border: 1px solid #ddd; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends PureComponent { render() { console.log("子组件渲染/更新"); return <div>我是子组件</div>; } } export default function Demo() { console.log("父组件渲染/更新"); let [x, setX] = useState(10); const handle = () => { } return ( <DemoStyle> <Child handle={handle} /> <p>{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改X </Button> </DemoStyle> ); }
-
-
useCallback:可以确保每一次组件更新,都拿到第一次创建函数的堆内存地址(也就是组件更新的时候,不会重新创建新的函数了,一直使用第一次创建的函数)。
-
useCallback(callback,[]) 只有第一次渲染创建新的函数,后期组件每次更新,获取的都是第一次创建的函数。
import { Button } from "antd"; import { useState, useCallback, PureComponent, memo } from "react"; import styled from "styled-components"; // 一点破样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 300px; border: 1px solid #ddd; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends PureComponent { render() { console.log("子组件渲染/更新"); return <div>我是子组件</div>; } } export default function Demo() { console.log("父组件渲染/更新"); let [x, setX] = useState(10); const handle = useCallback(() => {}, []); return ( <DemoStyle> <Child handle={handle} /> <p>{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改X </Button> </DemoStyle> ); }
-
使用这种方式,会存在一个很严重的问题:函数中获取的状态值,也永远是闭包1中的。
-
但是也会带来一种优化:如果我们把函数作为属性传递给子组件「前提:子组件内部也对新老属性做了比较,如果发现一样,则不允许更新(类组件继承PureComponent或者自己设置shouldComponentUpdate;函数组件基于React.memo处理即可)」,因为每一次获取的函数都是一致的,导致传递的属性也是一样的,则子组件不会再更新了。
import { Button } from "antd"; import { useState, useCallback, PureComponent, memo } from "react"; import styled from "styled-components"; // 一点破样式 const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 300px; border: 1px solid #ddd; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; // 函数组件中,如果想对新老属性做比较,从而控制视图更新或者不更新,需要使用 React.memo 方法 const Child = memo(function Child(props) { console.log("子组件渲染/更新"); return <div>我是子组件</div>; }); export default function Demo() { console.log("父组件渲染/更新"); let [x, setX] = useState(10); const handle = useCallback(() => {}, []); return ( <DemoStyle> <Child handle={handle} /> <p>{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改X </Button> </DemoStyle> ); }
- 函数组件中,如果想对新老属性做比较,从而控制视图更新或者不更新,需要使用 React.memo 方法。
-
-
useCallback(callback,[x]) 只有第一次渲染和x状态更新,才会重新创建新的函数。
- 这种写法没什么问题,就是用法不够简洁。
- 同时如果如果改了钩子函数导致依赖了其它的状态,那么还要改useCallback的第二个参数中的依赖数组,如果不及时同步改,可能导致函数中使用的状态不是自己预期的,而是某次闭包中的值。
- 这种写法没什么问题,就是用法不够简洁。
-
useCallback(callback) 和不设置useCallback一样,每一次渲染都会创建新的函数。
- 这个一般没人使用,因为和不使用useCallback作用一样,但不写useCallback,会让代码更简洁。
-
测试经过
-
如果给子组件传递一个在父组件中声明的变量,会发现子组件必定会在父组件更新的情况下触发更新,即便父组件的函数代码一直没变。
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends PureComponent { render() { console.log(`子组件渲染与更新`); return <div>子组件</div>; } } export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); // 父组件的每一次更新,都是把函数重新执行,产生一个新的闭包,闭包中的handle方法也会重新创建一个新的函数,即新的堆内存。这样每一次传递给Child的属性值,都是新的堆内存地址。所以不论子组件继承的是PureComponent还是Component,每一次更新,其接收的属性值都是发生变化的,所以子组件也要更新。 // 但是函数组件本应如此,只有每一次更新,都创建新的函数出来,这样在函数中所使用的状态,才是当前闭包中的信息! // 如果每次更新,handle方法依然是闭包一中的handle,那么在handle中,我们使用的状态,也依然是闭包一中的状态! const handle = () => { console.log(x); }; handle(); return ( <DemoStyle className="demo-box"> <Child handle={handle}></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
- 父组件的每一次更新,都是把函数重新执行,产生一个新的闭包,闭包中的handle方法也会重新创建一个新的函数,即新的堆内存。这样每一次传递给Child的属性值,都是新的堆内存地址。所以不论子组件继承的是PureComponent还是Component,每一次更新,其接收的属性值都是发生变化的,所以子组件也要更新。
- 但是函数组件本应如此,只有每一次更新,都创建新的函数出来,这样在函数中所使用的状态,才是当前闭包中的信息!
- 如果每次更新,handle方法依然是闭包一中的handle,那么在handle中,我们使用的状态,也依然是闭包一中的状态!
- 但是函数组件本应如此,只有每一次更新,都创建新的函数出来,这样在函数中所使用的状态,才是当前闭包中的信息!
- 父组件的每一次更新,都是把函数重新执行,产生一个新的闭包,闭包中的handle方法也会重新创建一个新的函数,即新的堆内存。这样每一次传递给Child的属性值,都是新的堆内存地址。所以不论子组件继承的是PureComponent还是Component,每一次更新,其接收的属性值都是发生变化的,所以子组件也要更新。
-
useCallback:可以确保每一次组件更新,都拿到第一次创建函数的堆内存地址(也就是组件更新的时候,不会重新创建新的函数了,一直使用第一次创建的函数)
-
当子组件为类组件时,且做了更新时比对优化:
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; class Child extends PureComponent { render() { console.log(`子组件渲染与更新`); return <div>子组件</div>; } } export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); // useCallback:可以确保每一次组件更新,都拿到第一次创建函数的堆内存地址(也就是组件更新的时候,不会重新创建新的函数了,一直使用第一次创建的函数) const handle = useCallback(() => { console.log(x); }, []); handle(); return ( <DemoStyle className="demo-box"> <Child handle={handle}></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
- 发现父组件渲染时,子组件可以不更新。
-
当子组件为函数组件时,单纯使用useCallback没有什么用,如果父组件更新,则子组件必定更新:
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; const Child = function Child() { console.log(`子组件渲染与更新`); return <div>子组件</div>; }; export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); const handle = useCallback(() => { console.log(`x:${x}`); }, []); return ( <DemoStyle className="demo-box"> <Child handle={handle}></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
- 这个是因为函数组件,必定是会执行一遍,所以导致子组件必定更新。
-
子组件为函数组件时,使用React.useCallback配合React.memo一起使用:
-
子组件可以在父组件时不必强制被连带更新。
import { Button } from "antd"; import { useState, useCallback, Component, PureComponent, memo } from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; const Child = memo(function Child() { console.log(`子组件渲染与更新`); return <div>子组件</div>; }); export default function Demo() { let [x, setX] = useState(10); console.log(`父组件渲染与更新`); const handle = useCallback(() => { console.log(`x:${x}`); }, []); return ( <DemoStyle className="demo-box"> <Child handle={handle}></Child> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
-
因为React.memo()可以对父组件前后传的props进行前后比对,以便判断是否应该强制更新实际的子组件函数的数据。
-
-
自定义hook简易说明
- 自定义hook:
-
表现来看,就是一个以use开头的函数组件。
import { Button } from "antd"; import { useState, useCallback,} from "react"; import styled from "styled-components"; const DemoStyle = styled.div` box-sizing: border-box; margin: 20px auto; padding: 20px; width: 400px; border: 1px solid #ccc; p { font-size: 18px; line-height: 40px; } .ant-btn { margin-right: 10px; } `; const useChaoDuan = function useChaoDuan() { return [10, 20]; }; export default function Demo() { let [x, setX] = useState(10); let [m, n] = useChaoDuan(); console.log(`父组件渲染与更新`,m, n); return ( <DemoStyle className="demo-box"> <p>x:{x}</p> <Button type="primary" size="small" onClick={() => setX(x + 1)}> 修改x </Button> </DemoStyle> ); }
-