Hooks相关知识

https://zh-hans.react.dev/reference/react/hooks

useState

语法: const [state, setState] = useState(initialState); 如下是一个计数器组件:

"use client";
import { useState } from "react";

export default function HooksPage(params) {
  const [count, setCount] = useState(0);
  function add() {
    setCount(count + 1);
  }

  return (
    <>
      <div>
        <h2>当前Count:{count}</h2>
        <button onClick={add}>点我+1</button>
      </div>
    </>
  );
}

这种写法还存在一种潜在问题,假设我们修改数据的操作是一部完成的,例如我们需要延迟一秒后再增加计数值:

 setTimeout(() => {
      setCount(count+1);
 }, 1000);

这样写会发现一秒内不管我点击多少次,数据都只会增加一。为了解决这个问题: 在setState()时除了直接传递一个指定值以外,React还允许我们通过一个回调函数来修改state

setTimeout(() => {
      setCount((prevState) => prevState + 1);
 }, 1000);

useRef

语法: useRef(initialValue)返回 { current: initialValue } 上述例子我们可以看到,useState每次都会出发函数的重新渲染,但是useRef不会,我们可以尝试把计数器改成用useRef实现:

const countRef = useRef(0);
  function add() {
    countRef.current = countRef.current + 1;
    console.log("countRef:", countRef);
  }

  return (
    <>
      <div>
        <h2>当前Count:{countRef.current}</h2>
        <button onClick={add}>点我+1</button>
      </div>
    </>
  );

会发现控制台的值有在改变,但是数据并没有重新渲染

何时使用 ref

通常,当你的组件需要“跳出” React 并与外部 API 通信时,你会用到 ref —— 通常是不会影响组件外观的浏览器 API。以下是这些罕见情况中的几个:

  • 存储 timeout ID
  • 存储和操作 DOM 元素
  • 存储不需要被用来计算 JSX 的其他对象。 如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。

聚焦输入框

"use client";
import { useRef } from "react";

export default function HooksPage(params) {
  const inputRef = useRef();

  function handleFocus() {
    inputRef.current.focus();
  }

  return (
    <>
      <div>
        <input ref={inputRef} type="text" />
        <button onClick={handleFocus}>聚焦输入框</button>
      </div>
    </>
  );
}

useEffect

React组件有部分逻辑都可以直接编写到组件的函数体中的,像是对数组调用filter、map等方法,像是判断某个组件是否显示等。但是有一部分逻辑如果直接写在函数体中,会影响到组件的渲染,这部分会产生“副作用”的代码,是一定不能直接写在函数体中。 例如,如果直接将修改state的逻辑编写到了组件之中,就会导致组件不断的循环渲染,直至调用次数过多内存溢出。 哪些代码不能直接写在组件内部呢?像是:获取数据、记录日志、检查登录、设置定时器等。简单来说,就是那些和组件渲染无关,但却有可能对组件产生副作用的代码。 用法:

useEffect(()=>{
    /* 编写那些会产生副作用的代码 */

    return () => {
        /* 这个函数会在下一次effect执行前调用 */
    };
    /* 监听a,b值改变后会调用 */
}, [a, b]);

回到计数器示例中,假设我们希望计数器默认开始就开始自增:

setInterval(() => {
    setCount((prev) => prev + 1);
  }, 1000);

会发现,数据增加的值会变非常大。原因是setCount方法重新触发了组件的渲染,定时器被不断声明。这就是“副作用”代码,合理的写法如下:

useEffect(() => {
    const timer = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);

获取数据

app/api/user/route.js创建一个api:

import { NextResponse } from "next/server";
import prisma from "../../lib/prisma";

export async function GET(req) {
  const user = await prisma.user.findFirst();
  return NextResponse.json(user);
}

使用 FetchAPI获取数据:

"use client";
import { useEffect, useState } from "react";

export default function HooksPage(params) {
  const [user, setUser] = useState({});

  function fetchData() {
    fetch("/api/user", {
      method: "GET",
    })
      .then((res) => res.json())
      .then((res) => {
        console.log(res);
        setUser(res);
      });
  }

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <div>
        <h2>UserId:{user?.id}</h2>
        <h2>UserName:{user?.name}</h2>
      </div>
    </>
  );
}

StrictMode

编写React组件时,我们要极力的避免组件中出现那些会产生“副作用”的代码。同时,如果你的React使用了严格模式,也就是在React中使用了React.StrictMode标签,那么React会非常“智能”的去检查你的组件中是否写有副作用的代码:

<React.StrictMode>
        <App />
</React.StrictMode>

Nextjs中,在next.config.js增加reactStrictMode: true:

/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: true
}

module.exports = nextConfig