React记录

127 阅读6分钟

useEffect的渲染

当监听一个Object或Function时,会在每次渲染时都触发。

所以应该用useMemo或useCallback等类似工具包裹后才能监听。

router

单页面应用的原理是改变url地址,会监听url变化,并切换组件。

history

使用popstate监听路由变化

window.addEventListener('popstate',function(e){ /* 监听改变 */ })

hash

使用onhashchange监听路由

window.addEventListener('hashchange',function(e){
    /* 监听改变 */
})

流程分析

当地址栏改变url,组件的更新渲染都经历了什么? 拿history模式做参考。当url改变,首先触发histoy,调用事件监听popstate事件, 触发回调函数handlePopState,触发history下面的setstate方法,产生新的location对象,然后通知Router组件更新location并通过``context上下文传递,switch通过传递的更新流,匹配出符合的Route组件渲染,最后有Route组件取出context内容,传递给渲染页面,渲染更新。

参考:「源码解析 」这一次彻底弄懂react-router路由原理 个人理解,单页面应用是使用一个html下,一次性加载js, - 掘金

状态存储

由于在每个组件的虚拟dom上存储状态时,是使用了数组的形式,并且以下标的方式去索引,所以不允许在判断中使用useState,否则可能造成索引不正确的问题

useRef

不会重新渲染

所以不适合存储希望显示在屏幕上的信息。

跨渲染周期

useState创建的对象会在每次渲染时都重置,无法获取上次渲染时的值。

而useRef创建的值可以跨渲染周期,所以适用于制作倒计时、计时器等。

本地信息

对于每个函数组件而言,创建在组件外部的变量是全局共享的。但是用 useref创建可以保证每个组件之间数据的隔离。

创建可以操作dom的变量

例如使用useref创建一个绑定子组件ref的变量,子组件通过forwardref暴露出ref方法

性能优化hooks useMemo useCallBack

memo

当父组件的参数变化时,父组件重新渲染,子组件也会随之渲染,但是子组件没有任何变化,这是无效的重复渲染,此时使用React.memo包裹子组件可以避免重复渲染。

const ChildComponent = memo(() => {
  console.log('子组件执行了');
  return (
    <p>我是子组件的内容</p>
  )
});

const ParentComponent = () => {
  console.log('父组件执行了');
  const [count, setCount] = useState<Number>(1);

  const changeCount = () => {
    setCount(count + 1);
  };

  return (
    <>
      <button onClick={changeCount}>点击</button>
      <p>count is: </p>
      <p>{count}</p>
      <ChildComponent />
    </>
  )
};

useCallBack

useCallback 是一个用于优化性能的 React Hook,它的主要作用是避免在每次渲染时都重新创建函数。通过记住上一次创建的函数,只有在依赖项发生变化时才重新创建新的函数,从而提高性能。

useCallback 接受两个参数:

  1. 一个函数,这个函数是我们需要记住的函数。
  2. 一个依赖项数组,当数组中的依赖项发生变化时,才会重新创建新的函数。

父组件的state中有数据变化导致重新渲染,即使子组件没有任何变化也会重新渲染,这是因为如果只使用memo,memo检测到的是props数据中栈地址是否发生变化。而父组件重新构建时会重新构建父组件中的所有函数(旧函数销毁,新函数创建,等于更新了函数地址)。 此时就可以用useCallBack包裹传给子组件的函数。

const parent = props => {
  const [num, setNum] = useState(0);

  const getValue = useCallback(value => {
    console.log(value);
  }, []);

  const changeState = () => {
    setNum(val => val + 1);
  };
  
  return (
    <div>
      我是父组件
      <Button onClick={changeState}>点我改变state</Button>
      {num}
      <child getValue={getValue} />
    </div>
  );
};

useMemo

用于优化渲染性能useMemo 会接收一个箭头函数包裹的回调函数依赖项数组,然后返回回调函数的计算结果。当依赖项数组中的某个值发生变化时,useMemo 会重新计算回调函数。如果依赖项没有发生变化,useMemo 会返回上一次计算的结果,这样可以避免不必要的计算。如下,只有在a或者b发生改变的时候,value的值才会重新计算。

会缓存计算出来的值,只有当依赖值发生变化(即使依赖值被重新赋值,但是由于判断出来new值和old值是相等的,那么依旧判定为无变化)时,才会重新计算

import React, { useState , useMemo } from "react";

function App() {

  const [introduction, setIntroduction] = useState("店长很懒什么都没写...")

  // 桃子的单价和价格
  const [peach, setPeach] = useState({
    num: 10,
    unitPrice: 5
  });

  // 香蕉的单价和价格
  const [banana, setBanana] = useState({
    num: 10,
    unitPrice: 10
  });

  const change = () => { setIntroduction("这是一家水果店") }

  const price = useMemo(()=>{
  
    console.log("价格重新计算了--");
    return (peach.num * peach.unitPrice) + (banana.num * banana.unitPrice)
    
  },[peach,banana])

  const addLike = () => {
    setLikeCount(likeCount + 1)
  }

  const change = () => {
    setPeach({
      ...peach,
      unitPrice: 20
    })
  }

  return (
    <div>
      <h3>店铺点赞数:{likeCount}</h3>
      <button onClick={addLike}>点赞</button>
      <button onClick={change}>修改桃子的单价</button>
      
      <h4> 购买的水果清单: </h4>
      <h4>  桃子数量: {peach.num}  单价:{peach.unitPrice}元 </h4>
      <h4>  香蕉数量: {banana.num} 单价:{banana.unitPrice}元 </h4>

      价格:{price}元

    </div>
  );
}

Route

完整route
import { NavLink, Routes, Route } from "react-router-dom";
import Product from "./Product";
import About from "./About";
import Home from "./Home";
import Error from "./Error";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <header>
        <h1>Hello World</h1>
      </header>
      <nav>
        <NavLink to="">首页</NavLink>
        <NavLink to="product">产品</NavLink>
        <NavLink to="about">关于</NavLink>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/product" element={<Product />} />
        <Route path="/about" element={<About />} />
        <Route path="*" element={<Error />} />
      </Routes>
    </div>
  );
}

useState原理

陷阱

调用set函数不会改变已经执行的代码中当前的state。它只影响下一次渲染中useState返回的内容。

function handleClick() {  
    setName('Robin');  
    console.log(name); // Still "Taylor"!  
}

利用闭包

function useState<T>(initialState: T): [T, (newState: T) => void]{ 
    state = state || initialState; 
    function setState(newState: T) { 
        state = newState;
        render(); 
    } 
    return [state, setState]; 
}

这样来看,vue的数据注册也是使用闭包?

Redux和context

Redux:利用context和provider构造出来的

倒计时问题

如果仅仅使用useEffect和useState会导致每次刷新页面时,且useState是异步,监听不准确。

此时使用useRef解决问题。

import React, { useEffect, useRef } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)
  const timerRef = useRef()

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)
    return () => clearInterval(timerRef.current)
  }, [])

  return <div>{count}</div>
}

useEffect

模拟生命周期

接收两个参数,第一个参数必传,是个函数,触发后执行。第二个参数非必传,是个数组。

1.不传数组

将在页面第一次刷新以及每次更新渲染后触发,即mount和update

2.传空数组

将在页面第一次刷新后触发,即mount

3.传有数据的数组

将在监听的数据发生变化时触发,即watch

4.第一个参数(函数)return一个函数,且第二个参数为空数组

将在页面销毁前触发,即componentWillUnmount

父组件调用子组件的函数

函数式组件: 简单来说,父组件向子组件的props传递一个用useRef()创建出来的变量,然后在子组件中使用useImperativeHandle()(可选操作),并且用forwardRef包裹子组件

父组件
const childRef = useRef()
<Child childRef={childRef} />
子组件
const { childRef } = props
useImperativeHandle(childRef, () =>({
    console.log('调用子组件组件')
}))
包裹子组件
let ForwardChild = forwardRef(Child);
此时在父组件中使用ref.current去调用
childRef.current.get()

如不需要useImperativeHandle(最简单) 首先用useRef创建变量,然后绑定到子组件的ref上,然后用forwardRef包裹子组件,父组件就可以用变量.current调用,此时子组件接收两个参数,一个是props一个是传进来的ref,且此时父组件需要引入的是包裹后的子组件

父组件
const childRef = useRef()
<Child ref={childRef} />
包裹子组件
let ForwardChild = forwardRef(Child);
此时在父组件中使用ref.current去调用
childRef.current.get()

路由传参及跳转的四个hooks

1.useNavigate 用于前一个页面传递参数

const navigate = useNavigate()
navigate('url',{state:value})

2.useLocation usePatams useSearchParams 用于后一个页面接受参数

3.useHistory 老hooks 类似$router

4.传参的两种方式:state和search search类似于query state刷新后会消失