关于React 6个你需要注意的地方

1,勿滥用 useState,非受控组件可以直接通过 ref 从 dom 上取值

import React, { useState } from "react"

type Props = {}

export default function Form({}: Props) {
    const [email, setEmail] = useState("")
    const [password, setPassword] = useState("")
    function onSubmit(e) {
        e.preventDefault()
        console.log({ email, password })
    }
    return (
        <form onSubmit={onSubmit}>
            <label htmlFor="email">Email</label>
            <input
                type="email"
                id="email"
                onChange={e => setEmail(e.target.value)}
            />
            <label htmlFor="password">Password</label>
            <input
                type="password"
                id="password"
                onChange={e => setPassword(e.target.value)}
            />
            <button type="submit">Login</button>
        </form>
    )
}

这个代码看着似乎没有任何问题,运行起来也很好。可是我们会发现这个email,password只在提交表单的时候会使用,而每次我们进行输入改变这个状态变量的时候都会让组件重新渲染,这并不好。所以下面这个版本,使用 ref 能很好解决这个问题。

import React, { useRef } from "react"

export default function Form() {
    const emailRef = useRef()
    const passwordRef = useRef()
    function onSubmit(e) {
        e.preventDefault()
        console.log({
            email: emailRef.current.value,
            password: passwordRef.current.value,
        })
    }
    return (
        <form onSubmit={onSubmit}>
            <label htmlFor="email">Email</label>
            <input ref={emailRef} type="email" id="email" />
            <label htmlFor="password">Password</label>
            <input ref={passwordRef} type="password" id="password" />
            <button type="submit">Login</button>
        </form>
    )
}

这种写法不仅更加简洁优雅而且可以避免在输入的时候组件重新渲染。我们在写表单的组件的时候可以尝试用useRef替代useState。

2,关于useState 

import React, { useState } from "react"

export default function Count() {
    const [count, setCount] =useState (0)
    function fn(num){
        setCount(count+num)
        setCount(count+num)
    }
    return (
        <>
            <button onClick={()=>{fn(-1)}}>-</button>
            {count}
            <button onClick={()=>{fn(+1)}}>+</button>
        </>
    )
}

会发现计数器函数fn虽然执行了两次setCount但是还是一次只加1,这是因为React的状态更新是异步的,所以两次setCount调用会在同一渲染周期中处理。每次setCount的时候都是取一开始count的状态。

    function fn(num) {
        setCount(currentCount => {
            return currentCount + num
        })
        setCount(currentCount => {
            return currentCount + num
        })
    }

而如果这么写的话,就会发现一次点击会加2。这是因为这种写法使用了函数形式的setCount,函数接受了currentCount作为参数,确保了在每次更新状态时都是基于最新的状态。因此,这两次更新将各自增加num的值到状态中。

3,useState的异步解决方案

    function fn(num) {
        setCount(currentCount => {
            return currentCount + num
        })
        console.log(num);
    }

由于setState是一个异步函数,所以在使用后直接使用state状态的时候,并不会取最新值。我们可以使用useEffect监听state的变化,代码也很简单。

    function fn(num) {
        setCount(currentCount => {
            return currentCount + num
        })
    }
    useEffect(() => {
        console.log(count)
    }, [count])

4, 请勿滥用useEffect

import React, { useEffect, useState } from "react"

export default function Count() {
    const [firstName, setFirstName] = useState("")
    const [lastName, setLastName] = useState("")
    const [fullName, setFullName] = useState("")
    useEffect(() => {
        setFullName(`${firstName} ${lastName}`)
    }, [firstName, lastName])
    return (
        <>
            <input
                type="text"
                onChange={e => {
                    setFirstName(e.target.value)
                }}
            />
            <br />
            <input
                type="text"
                onChange={e => {
                    setLastName(e.target.value)
                }}
            />
            <br />
            {fullName}
        </>
    )
}

这样写的话,也是能正常工作运行。但是会发现,用useEffect监听以及在使用setFullName的时候多少有些冗余以及同样的重复渲染。

    const [firstName, setFirstName] = useState("")
    const [lastName, setLastName] = useState("")
    const fullName = `${firstName} ${lastName}`

什么时候用useState,什么时候直接用const,值得我们去实践与思考。

5,useMemo实用小技巧

import React, { useEffect, useState } from "react"

export default function Count() {
    const [lastName, setLastName] = useState("")
    const [firstName, setFirstName] = useState("")
    const [num, setNum] = useState(0)
    const fullName = {
        lastName,
        firstName,
    }
    useEffect(() => {
        console.log(fullName)
    }, [fullName])
    return (
        <>
            <input
                type="text"
                onChange={e => {
                    setLastName(e.target.value)
                }}
            />
            <br />
            <input
                type="text"
                onChange={e => {
                    setFirstName(e.target.value)
                }}
            />
            <br />
            <button onClick={() => setNum(num + 1)}>click</button>
            {num}
        </>
    )
}

大家可以复制代码到本地实践一下。我们会很神奇的发现,我们不仅在输入框中输入的时候会输出fullName,在点击button按钮的时候也会输出fullName!为什么呢? 原来,这是因为每次setNum的时候,react会重新渲染组件,所以会从上到下读一遍代码,当读到

 const fullName = {
        lastName,
        firstName,
    }

的时候。react会对fullName重新赋值。也就是说fullName会改变(虽然看起来对象似乎没有改变,但我们知道  {} === {} 是一个false ,所以重新赋值之后就是不一样的对象了)。然后就会执行useEffect函数。

怎么避免这种情况呢,我们可以使用useMemo。

    const fullName = useMemo(() => {
        return {
            lastName,
            firstName,
        }
    }, [lastName, firstName])

把fullName这个对象用useMemo写,并监听键,就可以不会反复的去让fullName去重新赋值了。可能现在看来,有些例子的钩子用法看着跟原来的写法也没啥区别,可是等工作的时候,就会发现这些基础知识知道了就是知道了,并且会在某个瞬间让你恍然大悟。笔者就深有体会,在某一天改bug的时候,看到同事写的代码中用了useEffect监听一个字段,当你要对这个字段进行维护的时候,就会发现很容易出问题,但是如果你对useEffect的各种用法烂熟于心,就会在维护的过程中得心应手很多。还有的时候看到useMemo的时候也不会想为什么要在这用,不用的话会产生什么影响之类的疑惑了。还有就是呢,面试也爱问useMemo,useCallback这些,到时候面试官一问useMemo你把我这个例子给他这么一说,也能对你印象好不少。

6,一个实用的自定义钩子

最后是一个自定义钩子,也是面试官爱问的东西。笔者在面试小米的时候就有被问到平时是否有自己写过自定义钩子,返回值是什么之类的。

import { useEffect, useState } from "react"

const useFetch = url => {
    const [loading, setLoading] = useState(true)
    const [data, setData] = useState()
    const [error, setError] = useState()
    useEffect(() => {
        const controller = new AbortController()
        setLoading(true)
        fetch(url, { signal: controller.signal })
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false))
        return () => {
            controller.abort()
        }
    }, [url])
    return { loading, data, error }
}

export default useFetch

亮点在于当url输入时候导致频繁改变的时候,会触发  controller.abort() 清理副作用,取消请求,也就是只有最后一次请求被完全处理。 例如在处理实时搜索时,你可能只关心用户最后一次输入的搜索词。然而,在其他情况下,你可能想要在useEffect的依赖数组中包含更多的变量,以确保每次这些变量发生变化时都触发请求。controller.abort() 的作用是确保在组件卸载时取消正在进行的Fetch请求,以避免潜在的问题和资源泄漏,是一种处理异步操作的良好实践。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值