react常用的3个hooks

2023-3-23 1,037 3/23

 

先看一下常用与不是那么常用的钩子,在看看实际开发中是如何去使用的。

 

核心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的值,react常用的3个hooks numberTow的值累加到了13,而useState的回调函数也只执行了一次

 

那么如果使用useCallback去做一下缓存,react常用的3个hooks react常用的3个hooks,如果在对应的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的值react常用的3个hooks 页面并无刷新,且在我改变状态后,值也不会重置react常用的3个hooks

其实这种的话在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拿到的对象做一个对比,看是不是同一个引用

react常用的3个hooks true,是的

 

 

- THE END -
1

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论