先看一下常用与不是那么常用的钩子,在看看实际开发中是如何去使用的。
核心useState,mvvm框架,react的核心思想就数据驱动视图,不过与Vue的响应式有比较大的区别,可以理解为Vue是自动的去收集的依赖,当他监听的数据变化时便会去改变我们的视图,vue是通过新旧的虚拟dom的diff去对比出他认为变化了的dom在更新对应的真实dom,而react的话我们是需要去收到收集依赖的,我们去改变state都是通过setState去做的。而且在react16之后是通过diff与fiber的diff去进行的找到需要更新的dom,这个阶段是可以打断的,也就是调和,调度。
useState他可以接收一个值与一个回调函数,包括setState的参数也可以接收一个值与回调函数
那么他们的区别在哪?
useState一般用回调函数去赋默认值的话,这个回调函数在组件存在的过程中只会执行一次,一般的话,我们有时赋给useState的值还需要经过较为复杂的计算,那么如果这种情况会在每次state刷新都会重新计算一次,我么便可以通过回调函数进行处理。
而setState的回调函数的特点是,你可以在回调函数中拿到当做最新的state的值,就加入你使用了useMemo,useCallback这种,你完全可以不去给useMemo,useCallback添加对应的依赖项,直接使用setState中的回调函数,来拿到当前state的最新值再去处理
举个栗子
import React, { useCallback, useRef, useState } from "react";
import "./index.css";
function App() {
const [numberOne, setNumberOne] = useState(0)
const [numberTow, setNumberTow] = useState((state) => {
//做自己的逻辑判断
console.log('执行')
return 0
})
const changeNumOne = useCallback(() => {
console.log('修改numberOne的函数执行');
setNumberOne(numberOne + 1)
}, [numberOne]) //修改one
// const changeNumOne = useCallback(() => {
// console.log('修改numberOne的函数执行');
// setNumberOne(numberOne + 1)
// }, []) //修改one
const changeNumTow = useCallback(() => {
setNumberTow(num => ++num)
}, [])//修改tow
return (
<>
<button onClick={changeNumOne}>修改one</button><br />
<button onClick={changeNumTow}>修改tow</button><br />
number1:{numberOne}<br />
number2:{numberTow}
</>
);
}
export default App;
改变numberTow的值,
numberTow的值累加到了13,而useState的回调函数也只执行了一次
那么如果使用useCallback去做一下缓存,
,如果在对应的useCallback后没有添加对应的依赖项,可以看到函数虽然执行了,但是我们的state却没有改变,视图没有刷新,因为numberOne一直都是第一次改变后的值,后序在去执行setState 时他通过对比发现state的值一样便不会更新
我们添加依赖项后边可以正常更新,因为useCallback的依赖项变化后,他会刷新他所缓存的函数对象
不过也可以直接在useCallback中通过setState的回调函数去进行更新,因为在setState的回调函数他的参数永远都是state最新的值。
useEffect, 通常用于暂时“跳出” React 代码并与一些 外部 系统进行同步。这包括浏览器 API、第三方小部件,以及网络等等,在业务中,通常都是用来初始化数据,清除订阅,拿到真实dom的引用这些
其实useEffect是同步执行的,他是在整个渲染流程的commit阶段的最开始的阶段去处理的effect链表(commit阶段也会分为三个小阶段,渲染前,渲染中,渲染后),这时其实还没渲染真实dom,那么我们能拿到真实dom的原因是因为useEffect的回调函数是异步的,是在我们整个页面渲染完成,呈现完毕之后在去执行的。他是不会阻塞渲染的,自己之前有去debug过。当然与useEffect相似的还有useLayoutEffect,他们的区别一个是执行时机不同,而且useLayoutEffect的回调函数是同步执行的
useEffect接收两个参数,第一个是回调函数,第二个是一个数组(依赖项),可以在回调函数中返回一个函数,其实因为优选级的问题,react只能保证在该useEffect下一次执行之前一定会执行上一次useEffect返回的函数,所以其实有些说法也是错的,例如返回的函数是在组件卸载前执行。那是因为你传入的依赖项是一个空的数组。
项目中的代码
import React, { useEffect, useState } from 'react';
import style from './index.less'
import { Space, Tree, Input, } from 'antd';
import Table from '../Interfaces/components/table'
import { PlusCircleOutlined, FileOutlined, MinusCircleOutlined, EditOutlined } from '@ant-design/icons';
import { queryDirectory } from '@/services/interManage/controller';
const { DirectoryTree } = Tree;
const App: React.FC = () => {
const [treeData, SetTreeData] = useState([])
const queryData = async () => {
const treeData = await queryDirectory()
console.log('查询到的数据', treeData)
SetTreeData(treeData)
}
useEffect(() => {
queryData()
}, [])//TODO 初始化
return (
<div className={style.main1}>
<div className={style.treeList}>
<h3 style={{ textAlign: 'center' }}>数据资产目录<PlusCircleOutlined onClick={() => { }} /></h3>
<DirectoryTree
expandAction={'click'}
showIcon={false}//关掉默认icon 会有显示的问题 会单独占用一行
fieldNames={{ key: 'id', children: 'list' }}//id为key
titleRender={
(nodeData: any) => {
return <span
className={style.treeSpan}
key={nodeData.id}
//设置移入时显示
>
<FileOutlined />
{nodeData.classifyName}
<Space className={style.treeSelect}>
<PlusCircleOutlined />
<MinusCircleOutlined />
<EditOutlined />
</Space>
</span>
}
}//用css实现鼠标移入移出的显示效果
treeData={treeData}
/>
</div>
<div className={style.table}><Table /></div>
</div>
);
};
export default App;
基本最常用的就是利用他来初始化数据,因为他是的回调函数异步的
我们在使用一些状态管理工具时,需要在组件卸载时去清除订阅,只需要在对应的useEffect中返回对应的函数即可
import useBearStore, { bearStoreType } from "@/models"
import { useEffect } from "react"
export default () => {
const { num,subtractNum, declarNum } = useBearStore(store => store)
useEffect(() => {
return declarNum
}, [])
return <>
<button onClick={subtractNum}>子组件1,点我减一</button><br />
</>
}
自己去使用过高德地图,高德地图的话他的初始化是需要拿到真实dom的引用的,然后再去初始化,
之前封装过的高德地图
import React, { LegacyRef, useEffect, useRef } from 'react';
import { message, Input } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import AMapLoader from '@amap/amap-jsapi-loader';
import './Map.less';
// 编辑地图 传出选址的位置信息 经纬度 code 地址名 新增或修改
type GmapType = {
LngLat?: [] as Number; //经纬度 如果为undefined 则为新增 否则为修改 地图center值为传入的经纬度
getAddressData: (AddressData: {}) => any; //传入方法 获得地址数据
};
export default function GMap(props: GmapType) {
const { LngLat, getAddressData }: any = props;
const analyzeAddress = (result: any) => {
const { lng, lat } = result.position; //经纬度
const { address } = result; //详细地址
const fullAddress = address.slice(address.indexOf('区', '县') + 1);
const { adcode, province, city, district } =
result.regeocode.addressComponent; //地址信息
return {
latitude: lat,
longitude: lng,
fullAddress: fullAddress,
addressName: `${province}-${city}-${district}`,
addressCode: adcode,
};
}; //处理地图中得到的地址结构 满足返回信息的格式
const mapRef = useRef<any>({});
const inputRef = useRef<any>({}); //搜索框
async function loadGMap() {
const AMap = await AMapLoader.load({
key: '申请的key',
version: '2.0',
plugins: [],
AMapUI: {
version: '1.1',
plugins: ['misc/PositionPicker'],
},
});
const map = (mapRef.current.map = new AMap.Map('g-map-container', {
mapStyle: 'amap://styles/fresh',
zoom: 18,
center: LngLat,
lang: 'zh_cn',
viewMode: '3D',
pitch: 30,
resizeEnable: true, //设置缩放
keyboardEnable: false,
}));
AMap.plugin(
['AMap.ToolBar', 'AMap.Scale', 'AMap.AutoComplete', 'AMap.PlaceSearch'],
() => {
map.addControl(new AMap.ToolBar()); //控制缩放
map.addControl(new AMap.Scale()); //比例尺
const autocomplete = new AMap.AutoComplete({
input: inputRef.current.input,
});
const placeSearch = new AMap.PlaceSearch({
map: map,
}); //构造地点查询类
autocomplete.on('select', function (result: any) {
//TODO 针对选中的poi实现自己的功能
placeSearch.search(result.poi.name)
});
},
); //地图插件集的处理
const positionPicker = new AMapUI.PositionPicker({
mode: 'dragMap', //dragMarker设置为不可拖拽
map: map,
});
positionPicker.start(); //设置开拖拽模式
positionPicker.on('success', (result: any) => {
getAddressData(analyzeAddress(result));
}); //成功获得拖拽后周边的结果
positionPicker.on('fail', () => {
message.error('无法获取当前位置信息');
});
}
useEffect(() => {
window._AMapSecurityConfig = {
securityJsCode: 'key对应的密匙',
};
loadGMap();
}, [LngLat]);//当传入的经纬度改变时地图会重新渲染
return (
<>
<div className="mapComponent">
<div id="g-map-search">
<Input
placeholder={`输入地址`}
prefix={<SearchOutlined />}
size="middle"
style={{ width: '250px' }}
ref={inputRef}
/>
</div>
<div id="g-map-container"></div>
</div>
</>
);
这类的需求就需要拿到真实dom的引用了。
useRef,这个一般可以用来缓存数据或者去拿真实dom的引用,有时我们需要缓存下我们的数据,但是当我们数据改变时又不需要去重新渲染,那么useRef是一个很好的选择。
先举例,缓存数据
import React, { useCallback, useRef, useState } from "react";
import "./index.css";
function App() {
const [flag, setFlag] = useState(false)
const myRef = useRef(0)
const updateMyRef = () =>{ ++myRef.current;console.log('ref当前值',myRef.current)}
const updateFlag = () => {
setFlag(state => !state)
console.log('我缓存的ref的值', myRef.current)
}
return (
<>
<button onClick={updateFlag}>修改flag</button><br />
<button onClick={updateMyRef}>改变我的ref换缓存的数据</button><br />
number1:{flag}<br />
我的ref引用的值{myRef.current}
</>
);
}
export default App;
当我改变了ref的值
页面并无刷新,且在我改变状态后,值也不会重置
其实这种的话在antd中写那个分页时是需要用到ref去缓存分页参数的
第二种的话是拿到引用
import React, { useCallback, useEffect, useRef, useState } from "react";
import "./index.css";
function App() {
const divRef = useRef(null)
useEffect(() => {
const doDiv = document.querySelector('#myDiv')
const refDIv = divRef.current
console.log('是否相等', doDiv === refDIv)
console.log('doDiv', doDiv)
console.log('refDiv', refDIv)
}, [])
return (
<div id="myDiv" ref={divRef} >被引用的div</div>
)
}
export default App;
我们以ref与document拿到的对象做一个对比,看是不是同一个引用
true,是的
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:http://www.cx330.cloud/index.php/2023/03/23/react%e7%9a%84hooks/
共有 0 条评论