千峰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

相关推荐
风中飘爻7 分钟前
JavaScript:基本语法
开发语言·前端·javascript
桃子不吃李子15 分钟前
前端学习10—Ajax
前端·学习·ajax
狂炫一碗大米饭27 分钟前
快刷每日面经😋
前端·面试
涵信34 分钟前
第二节:React 基础篇-受控组件 vs 非受控组件
前端·javascript·react.js
二月垂耳兔7981 小时前
jQueryHTML与插件
前端·jquery
quo-te1 小时前
AJAX简介
前端·ajax·okhttp
bingbingyihao1 小时前
通过代码获取接口文档工具
开发语言·前端·javascript
月伤591 小时前
JS中Map对象与数组的相互转换
前端·javascript·html
SEO_juper3 小时前
解密 URL 参数:如何利用它们提升网站性能和用户体验
前端·javascript·ux·seo·url·数字营销·谷歌seo
nuIl3 小时前
让 Cursor 帮你把想法落地
前端·ai编程