千峰React:Hooks(下)

useLayoutEffect

useLayoutEffect在useEffect之前触发

这样会闪屏,因为是异步的,两次都渲染了

javascript 复制代码
import {useEffect,useState } from 'react';

function App() {
  const [msg,setMsg] = useState('hello App')

  useEffect(() => {
    setMsg('hello useEffect')
  });

  return (
    <div >
      {msg}
    </div>
  );
}

export default App;

换上useLayoutEffect不闪屏,在异步的情况是等两者都执行完以后渲染

javascript 复制代码
import {useLayoutEffect,useState } from 'react';

function App() {
  const [msg,setMsg] = useState('hello App')

  useLayoutEffect(() => {
    setMsg('hello useEffect')
  });

  return (
    <div >
      {msg}
    </div>
  );
}

export default App;

触发多次的时候还是useEffect好,解决闪屏问题用useLayoutEffect

useInsertionEffect Dom

触发顺序是123

javascript 复制代码
import { useEffect, useLayoutEffect, useState } from 'react'

function App() {
  //触发顺序:3->2->1
  useEffect(() => {
    console.log(1)
  })
  useLayoutEffect(() => {
    console.log(2)
  })
  useInsertionEffect(() => {
    console.log(3)
  })

  return <div></div>
}

export default App

有些团队喜欢在js里写样式,这叫CSS-in-Js

有三种实现方法:

useEffect和useLayoutEffect是在jsx渲染后走的样式,会造成浏览器的频繁计算,有性能影响,一般都用useInsertionEffect

javascript 复制代码
import { useInsertionEffect, useRef } from 'react'

function App() {
  const ref = useRef(null)
  
    useInsertionEffect(() => {
        const style = document.createElement('style')
        style.innerHTML = `
        .box{
        background:red;
        width:100px;
        height:100px;
        }`
        document.head.appendChild(style)
  })

    return <div className='box' ref={ref}>哈哈哈</div>
}

export default App

Reducer和Context

Reducer的统一状态管理集合

Reducer是整合状态的工具,我们之前学过useState来组织状态,Reducer可以规划复杂的组件逻辑

内里的逻辑其实就是switch

javascript 复制代码
import { useActionState, useReducer, useState } from 'react'

//由外部函数来完成逻辑操作
function listReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, { id: 4, text: 'ddd' }]
    case 'edit':
      return state.map((item) => {
        if (action.id === item.id) {
          return { ...state, id:action.id,text: 'new ' + item.text }
        } else {
          return item
        }
      })
    case 'remove':
      return state.filter((item) => {
        if (action.id === item.id) {
          return false
        } else return true
      })
  }
}

function App() {
  const [list, dispatch] = useReducer(listReducer, [
    { id: 1, text: 'aaa' },
    { id: 2, text: 'bbb' },
    { id: 3, text: 'ccc' },
  ])

  return (
    <div>
      <input type='text' />
      <button
        onClick={() => {
          dispatch({ type: 'add' })
        }}
      >
        添加
      </button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => {
                  dispatch({ type: 'edit', id: item.id })
                }}
              >
                编辑
              </button>
              <button
                onClick={() => {
                  dispatch({ type: 'remove', id: item.id })
                }}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App

之前我们学了Immer可以整合对状态变量的修改,其实useImmerReducer可以实现整合状态变量的修改+起到多个状态管理的作用

这是用immer改写的代码,效果也是一样的

javascript 复制代码
import { useImmerReducer } from 'use-immer'

//由外部函数来完成逻辑操作
function listReducer(draft, action) {
  switch (action.type) {
    case 'add':
      draft.push({ id: 4, text: 'ddd' })
      break
    case 'edit':
      const value = draft.find((item) => item.id === action.id)
      value.text='new '+value.text
      break
    case 'remove':
      const index = draft.findIndex((item) => item.id === action.id)
      draft.splice(index,1)
  }
}

function App() {
  const [list, dispatch] = useImmerReducer(listReducer, [
    { id: 1, text: 'aaa' },
    { id: 2, text: 'bbb' },
    { id: 3, text: 'ccc' },
  ])

  return (
    <div>
      <input type='text' />
      <button
        onClick={() => {
          dispatch({ type: 'add' })
        }}
      >
        添加
      </button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => {
                  dispatch({ type: 'edit', id: item.id })
                }}
              >
                编辑
              </button>
              <button
                onClick={() => {
                  dispatch({ type: 'remove', id: item.id })
                }}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App

Context向组件深层传递数据

props通常只能实现父子间的信息传递,Context是跨组件通信的一种方案

比如我们在这里三个组件,实现祖孙级别的通信👇

javascript 复制代码
function Tittle({ count }) {
  return (
    <div>
        Hello Tittle
      {'我是Tittle,这是我接收的参数:'}
      {count}
    </div>
  )
}

function Head({ count }) {
  return (
    <div>
        Hello Head
      <Tittle count={count} />
    </div>
  )
}

function App() {
  return (
    <div>
        Hello App
      <Head count={123} />
    </div>
  )
}
export default App

count通过App->Head,最后传给Tittle

如果使用Context就可以跨组件通信了

javascript 复制代码
import { createContext, useContext } from 'react'
const Context = createContext()//定义钩子,尽量大写

function Tittle() {
  const value = useContext(Context)//定义接收的参数
  return (
    <div>
      Hello Tittle
      {'我是Tittle,这是我接收的参数:'}
      {value}
    </div>
  )
}

function Head() {
  return (
    <div>
      Hello Head
      <Tittle />
    </div>
  )
}

function App() {
  return (
    <div>
      Hello App
      <Context.Provider value={123}>'{提供数据}'
        <Head />
      </Context.Provider>
    </div>
  )
}
export default App

这里传递的参数只有一个值,如果传递多个可以写成数组或者对象:

javascript 复制代码
function App() {
  return (
    <div>
      Hello App
      <Context.Provider value={[1,2,3,4,5]}>
        <Head />
      </Context.Provider>
    </div>
  )
}
javascript 复制代码
import { createContext, useContext } from 'react'
const Context = createContext()

function Tittle() {
  const value = useContext(Context)
  return (
    <div>
      Hello Tittle
      {'我是Tittle,这是我接收的参数:'}
      {'id:' + value.id}
      {'text:' + value.text}
    </div>
  )
}

function Head() {
  return (
    <div>
      Hello Head
      <Tittle />
    </div>
  )
}

function App() {
  return (
    <div>
      Hello App
      <Context.Provider value={{id:1,text:'aaa'}}>
        <Head />
      </Context.Provider>
    </div>
  )
}
export default App

但是不管传递多少个数据,只能通过value来传递

跨组件传递的状态变量,也会在渲染的时候重新传递、重新渲染

Reducer配合Context实现共享状态管理

实现父子通信用props,实现深层通信用context,实现兄弟间通信怎么做?

一个方法是使用状态提升,把状态提到两个兄弟的公共区域,再从公共区域通过父子通信的方式传回去,但是不够灵活

太复杂的可以用第三方库

但是React里,把这俩玩意一起用,Context可以跨组件通信,Reducer可以整合组件更新逻辑,也可以实现兄弟通信

六月份学长说建议以后使用redux,反正我也看不懂,就这样吧

memo

我们在父组件里调用子组件,父组件有改变,子组件内部无需渲染的情况下,其实还是会渲染的👇

可以看见子组件里面的随机数在改变

为了节省性能,可以使用memo来优化效率

javascript 复制代码
import { useState,memo } from 'react'

const Head = memo(function Head() {
  return (
    <div>
      Hello Head,{ Math.random()}
    </div>
  )
}
)
function App() {
  const [count, setCount] = useState(0)
  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <div>
      Hello App
      <button onClick={handleClick}></button>
        <Head />
    </div>
  )
}
export default App

可以看见随机数并没有改变

useMemo对计算结果进行缓存

javascript 复制代码
import { useState,memo } from 'react'

const Head = memo(function Head() {
  return (
    <div>
      Hello Head,{ Math.random()}
    </div>
  )
}
)
function App() {
  const [count, setCount] = useState(0)
  const [msg, setMsg] = useState('hello React')
  const list=[msg.toLocaleLowerCase(),msg.toLocaleUpperCase()]
  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <div>
      Hello App
      <button onClick={handleClick}></button>
        <Head list={list} />
    </div>
  )
}
export default App

按理来说,Head加了memo应该是不会重新刷新的对吧

但是其实刷新两次,随机数还是不同的

因为引用类型的判别(之前说过)

这时候要用useMemo来解决引用类型在Object.Is()方法被判定为不同导致重新渲染耗费性能的问题

useMemo是靠缓存上次结果,拿上次结果做对比的

javascript 复制代码
import { useState,memo,useMemo } from 'react'

const Head = memo(function Head() {
  return (
    <div>
      Hello Head,{ Math.random()}
    </div>
  )
}
)
function App() {
  const [count, setCount] = useState(0)
  const [msg, setMsg] = useState('hello React')
  const list = useMemo(() => [msg.toLocaleLowerCase(),msg.toLocaleUpperCase()])//只有msg被修改时,list才会重新计算,其他情况都是拿上次缓存的值
  
  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <div>
      Hello App
      <button onClick={handleClick}></button>
        <Head list={list} />
    </div>
  )
}
export default App

useCallback对函数进行缓存

上面的代码我们引用的是数组,这里写一个函数:

javascript 复制代码
import { useState,memo,useMemo } from 'react'

const Head = memo(function Head() {
  return (
    <div>
      Hello Head,{ Math.random()}
    </div>
  )
}
)
function App() {
  const [count, setCount] = useState(0)
  const [msg, setMsg] = useState('hello React')
  const fn = () => {
    console.log(msg)
  }
  //const list = useMemo(() => [msg.toLowerCase(),msg.toUpperCase()],[msg])//只有msg被修改时,list才会重新计算,其他情况都是拿上次缓存的值
  
  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <div>
      Hello App
      <button onClick={handleClick}>点我</button>
        <Head fn={fn} />
    </div>
  )
}
export default App

还是会多渲染,所以我们用useCallback,useCallback其实就是省略了useMemo里面套两个回调的写法:

javascript 复制代码
import { useState,memo,useMemo, useCallback } from 'react'

const Head = memo(function Head() {
  return (
    <div>
      Hello Head,{ Math.random()}
    </div>
  )
}
)
function App() {
  const [count, setCount] = useState(0)
  const [msg, setMsg] = useState('hello React')
  const fn = useCallback(() => {
    console.log(msg)
  },[msg])
  const handleClick = () => {
    setCount(count + 1)
  }

  return (
    <div>
      Hello App
      <button onClick={handleClick}>点我</button>
        <Head fn={fn} />
    </div>
  )
}
export default App

startTransition方法及并发模式

我们react居然还有并发

javascript 复制代码
import { useState,memo,useMemo, useCallback, startTransition } from 'react'

function List({ query}) {
  
  const items = []
  const word = 'hello World'
  if (query !== '' && word.includes(query)) {
    const arr = word.split(query)
    for (let i = 0; i < 1000; i++) {
      items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
    }
  } else {
    for(let i=0;i<1000;i++){
      items.push(<li key={i}>{ word}</li>)
    }
  }
  return (
    <ul>
      {items}
    </ul>
  )
}

function App() {
  const [search, setSearch] = useState('')
  const [query,setQuery]=useState('')
  const handleChange = (e) => {
    //紧急
    setSearch(e.target.value)
    //将这个任务设置为非紧急
    startTransition(() => { 
    setQuery(e.target.value)})
  }

  return (
    <div>
      Hello App
      <input type="text" value={search} onChange={handleChange} />
      <List query={ query} />
    </div>
  )
}
export default App

设计成非紧急任务以后,就先在输入框流畅的显示输入的字符串,再染色

如果两个都是紧急任务的话,就得等两个都完成

useTransition与useDeferredValue

在加载的时候显示loading

javascript 复制代码
import { useState,memo,useMemo, useCallback, startTransition ,useTransition} from 'react'

function List({ query}) {
  
  const items = []
  const word = 'hello World'
  if (query !== '' && word.includes(query)) {
    const arr = word.split(query)
    for (let i = 0; i < 1000; i++) {
      items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
    }
  } else {
    for(let i=0;i<1000;i++){
      items.push(<li key={i}>{ word}</li>)
    }
  }
  return (
    <ul>
      {items}
    </ul>
  )
}

function App() {
  const [search, setSearch] = useState('')
  const [query, setQuery] = useState('')
  const [pending, startTransition] = useTransition()
  const handleChange = (e) => {
    //紧急
    setSearch(e.target.value)
    //将这个任务设置为非紧急
    startTransition(() => { 
    setQuery(e.target.value)})
  }

  return (
    <div>
      Hello App
      <input type="text" value={search} onChange={handleChange} />
      {pending && <div>loading...</div>}
      <List query={ query} />
    </div>
  )
}
export default App

useDeferredValue的使用

javascript 复制代码
import { useState,memo,useMemo, useCallback, startTransition ,useTransition, useDebugValue, useDeferredValue} from 'react'

function List({ query}) {
  
  const items = []
  const word = 'hello World'
  if (query !== '' && word.includes(query)) {
    const arr = word.split(query)
    for (let i = 0; i < 1000; i++) {
      items.push(<li key={i}>{arr[0]}<span style={{color:'red'}}>{query}</span></li>)
    }
  } else {
    for(let i=0;i<1000;i++){
      items.push(<li key={i}>{ word}</li>)
    }
  }
  return (
    <ul>
      {items}
    </ul>
  )
}

function App() {
  const [search, setSearch] = useState('')
  const query = useDeferredValue(search)
  const handleChange = (e) => {
    //紧急
    setSearch(e.target.value)
  }

  return (
    <div>
      Hello App
      <input type="text" value={search} onChange={handleChange} />
      <List query={query} />
    </div>
  )
}
export default App

useId生成唯一id值

很少用,会在一些无障碍的操作用到

如果两次调用同一个组件,会发现这两个组件的id一样

javascript 复制代码
function MyInput() {
  return (
    <div>
      <label>密码:<input type="password" aria-describedby="password" /></label>
      <p id="password">密码至少包含18个字符</p>
    </div>
  )
}

function App() {
  return (
    <div>
      Hello App
      <MyInput />
      <MyInput />
    </div>
  );
}

export default App;

id相同👇但是最好不应该相同

javascript 复制代码
import { useId } from 'react-id-generator'

function MyInput() {
  const password = useId()
  return (
    <div>
      <label>密码:<input type="password" aria-describedby={password} /></label>
      <p id={password}>密码至少包含18个字符</p>
    </div>
  )
}

function App() {
  return (
    <div>
      Hello App
      <MyInput />
      <MyInput />
    </div>
  );
}

export default App;

生成两个不同的id

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax