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
...