14_React Hooks

React16.8之前Class组件的优势

  • class组件组件可以定义自己的state,
  • 有自己的生命周期如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次
  • 在状态改变之后只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等

而函数式组件不能定义自己的state,没有生命周期,在重新渲染时,整个函数都会执行。

Class组件存在的问题

  • 随着业务的增多,class组件会变得越来越复杂;各种逻辑混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
  • ES6的Class API是学习React的一个障碍,前端开发人员必须搞清楚this指向问题,但是依然处理起来非常麻烦
  • 在复用组件状态时需要通过高阶组件,过多的高阶组件嵌套会导致代码变得难以理解

Hooks的出现

为了解决Class组件存在的问题,React16.8推出了Hook特性。它可以让我们在不编写class的情况下使用state以及其他的react特性(比如生命周期),可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决。

Hook的使用场景:

  • Hook的出现基本可以代替我们之前所有使用class组件的地方;
  • 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
  • Hook只能在函数组件和自定义hook中使用,不能在类组件,或者函数组件之外的地方使用;

Hook是完全可选的 无需重写任何已有代码就可以在一些组件中尝试 Hook。

Hook是100% 向后兼容的 Hook 不包含任何破坏性改动。

Hook是现在可用 Hook 已发布于 v16.8.0。

案例对比

两个代码差异非常大,函数式组件结合hooks让整个代码变得非常简洁,并且再也不用考虑this相关的问题。

4_React4_React

Hook使用注意事项

  • 只能在函数组件和自定义hook中使用,不能在类组件,或者函数组件之外的地方使用;
  • 只能在函数的最外层调用,不能在循环、条件判断、或者子函数中调用

useState

useState需要从react中导入,能够为函数组件提供状态(state)。

可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。

  • 参数:初始化值,只会在组件第一次渲染时生效 不设置则为undefined;
    • 如果初始state需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用
  • 返回值:数组,包含两个元素
    • 元素一:当前状态的值
    • 元素二:修改状态值的函数。设置新的状态后组件会重新渲染,根据新的值返回DOM结构。

注意事项

  1. useState的初始值(参数)只会在组件第一次渲染时生效 也就是说,以后的每次渲染,useState获取到都是最新的状态值,React组件会记住每次最新的状态值
  2. 修改状态的时候,一定要使用新得状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
import { useState } from 'react'

function App({ message = '来自props' }) {
  // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
  // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState(() => {
    return 'Hello Hooks' + props.message
  })

  function changeMessage() {
    setMessage('你好, Hooks')
  }
  
  return (
    <h2>Message: {message}<h2>
    <button onClick={changeMessage}>修改文本</button>
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}
export default App

useReducer

useReducer和Redux并没有什么关系。

useReducer是useState的一种替代方案,在state的处理逻辑比较复杂时 或者 这次修改的state需要依赖之前的state时,可以使用useReducer。

const [state, dispatch] = useReducer(reducer, initialArg, init);

参数:

reducer : 对于我们当前state的所有操作都应该在该函数中定义,该函数的返回值,会成为state的新值
reducer在执行时,会收到两个参数:
state:当前最新的state
action:它需要一个对象,在对象中会存储dispatch所发送的指令
initialArg : state的初始值,作用和useState()中的值是一样
返回值:数组
第一个参数,state 用来获取state的值
第二个参数,state 修改的派发器,通过派发器可以发送操作state的命令,具体的修改行为将会由另外一个函数(reducer)执行

import React, { memo, useReducer } from 'react'
// import { useState } from 'react'

// 为了避免reducer会重复创建,通常reducer会定义到组件的外部
function reducer(state, action) {
  switch(action.type) {
    case "add_number":
      return { ...state, counter: state.counter + action.num }
    case "sub_number":
      return { ...state, counter: state.counter - action.num }
    default:
      return state
  }
}

const App = memo(() => {
  // const [count, setCount] = useState(0)
  // const [counter, setCounter] = useState()
  // const [user, setUser] = useState()
  const [state, dispatch] = useReducer(reducer, { 
    counter: 0, 
    user: {
      name: 'jay'
    } 
  })

  return (
    <div>
      {/* <h2>当前计数: {count}</h2>
      <button onClick={e => setCount(count+1)}>+1</button>
      <button onClick={e => setCount(count-1)}>-1</button>*/}

      <h2>当前计数: {state.counter}</h2>
      <button onClick={e => dispatch({ type: "add_number", num: 5 })}>+5</button>
      <button onClick={e => dispatch({ type: "sub_number", num: 5 })}>-5</button>
    </div>
  )
})

export default App

useEffect

Effect Hook 可以让我们在函数式组件中完成一些类似于class组件中生命周期的功能。

什么是副作用(Side Effects)?副作用是相对于主做用来说的,一个函数除了主做用,其他作用就是副作用。

对于React组件来说,主做用就是根据数据(state/props)渲染UI,除此之外都是副作用(比如:手动修改dom)。

类似于网络请求、手动更新DOM、事件监听、localStorage操作等都是一些React更新DOM的副作用(Side Effects)。

useEffect解析:

  • 通过useEffect告诉React需要在渲染后执行某些操作
  • useEffect要求传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数
  • 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个回调函数
  • 可以按照代码用途多次使用useEffect,React按照effect声明的顺序依次调用组件中的每一个effect

基础使用

import { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
 
  useEffect(()=>{
    // dom操作
    document.title = `当前已点击了${count}次`
  })
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}

export default App

依赖项控制执行时机

默认情况下,useEffect的回调函数每次渲染都会重新执行。某些情况下我们希望有些代码只执行一次即可,类似于在componentDisMount和componentWillUnmount完成的事情,另外多次执行也会导致一定的性能问题。

useEffect有两个参数,参数一是执行的回调函数,参数二决定useEffect在哪些state发生变化时,才会重新执行(依赖于谁)。

4_React

  • 不添加依赖项(默认): 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
  • 添加空数组: 组件只在首次渲染时执行一次
  • 添加特定依赖项: 副作用函数在首次渲染时和在依赖项发生变化时执行

注意事项:

useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现

import { useEffect, useState } from 'react'

function App() {  
	const [count, setCount] = useState(0)  
	const [name, setName] = useState('chou') 

	useEffect(()=>{
		console.log('副作用执行了:没有依赖项')
	})

	useEffect(()=>{
		console.log('首次渲染时执行')
	}, [])

	useEffect(() => {    
		console.log('副作用执行了,count:', count)  
	}, [count])  

	return (    
		<div>      
			<button onClick={() => { setCount(count + 1) }}>{count}</button>      
			<button onClick={() => { setName('jay') }}>{name}</button>    
		</div>  
	)
}

export default App

清除副作用

在Class组件中,像定时器、一些订阅是需要在componentWillUnmount中取消的。

在useEffect中需要在回调函数中return一个新的函数,在新的函数中编写清理副作用的逻辑。

为什么需要返回一个新函数:清除机制是可选的,每个effect都可以返回一个清除函数方便讲添加和移除订阅的逻辑放在一起。

执行时机:组件更新时(下一个useEffect副作用函数执行之前)和卸载时自动执行。

import { useEffect, useState } from "react"

const App = () => {
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => {
      // 清除副作用
      clearInterval(timer)
    }
  }, [count])
  
  return (
    <div>
      {count}
    </div>
  )
}

export default App

useEffect – 发送网络请求

不可以直接在useEffect的回调函数外层直接包裹await,因为异步会导致清理函数无法立即返回

// 错误做法
useEffect(async ()=>{    
    const res = await fetch('http://localhost/getUserInfo')   
    console.log(res)
},[])

// 正确做法
useEffect(()=>{   
    async function fetchData(){      
       const res = await fetch('http://localhost/getUserInfo')              
    	 console.log(res)   
    } 
},[])

useContext

在之前的开发中,在组件中使用Context有两种方式:

  • 类组件可以通过 类名.contextType = MyContext 的方式,在类中获取context
  • 多个context或者在函数式组件中使用 MyContext.Consumer 组件回调函数的方式消费context

但是使用MyContext.Consumer消费多个Context时会存在大量的嵌套。

useContext将某个Context作为参数,获取Context的值。可以使用多次,获取多个Context的值。

import { createContext, useContext } from 'react'

// 创建Context对象
const UserContext = createContext({name: 'jay'})
const ThemeContext = createContext({primaryColor: '#409eff'})

function Bar() {  
	// 底层组件通过useContext函数获取数据  
	const userCtx = useContext(UserContext)  
	const themeCtx = useContext(ThemeContext)  
	
	return (
		<div>
			<h2>Bar</h2>  
			<div>{userCtx.name}</div>>
		</div>
	)
}

function Foo() {  
	return (
		<div>
			<h2>Foo</h2> 
			<Bar/>
		</div>
	)
}

function App() {  
	return (    
		// 顶层组件通过Provider 提供数据    
		<Context.Provider value={'this is name'}>     
			<div>
				<Foo/>
			</div>    
		</Context.Provider>  
	)
}

export default App

useCallback

useCallback 允许你在多次渲染中缓存函数,在依赖不变的情况下,多次定义时,返回的函数是相同的。这种缓存返回值的方式叫做记忆化(memoization)。

constcachedFn = useCallback(fn,dependencies)

参数一:在多次渲染中需要缓存的函数

参数二:函数内部需要使用到的所有组件内部值的 依赖列表

useCallback只应该用于性能优化:

  • 跳过组件渲染
    • 普通函数在每次渲染时都会重新创建,如果传递给子组件,意味着子组件的props永远是不同的,这时memo对子组件性能的优化永远不会生效
    • 所以当需要将一个函数传递给子组件时,使用useCallback进行优化,将优化之后的函数,传递给子组件可以避免不必要的渲染
  • 优化自定义Hook: 使用useCallback包裹自定义hook中返回的函数,让使用者在需要时能够优化自己的代码
import React, { memo, useCallback, useState } from 'react';

const A = memo((props) => {
  console.log('A渲染');
  const { onAdd } = props
  const [count, setCount] = useState(1);
  const clickAHandler = () => {
    setCount(prevState => prevState + 1);
  };

  return (
    <div>
      <h2>组件A -- {count}</h2>
      <button onClick={clickAHandler}>增加</button>
      <button onClick={onAdd}>增加App</button>
      {/* 100个组件 */}
    </div>
  )
})

let temp = null

const App = () => {
  console.log('App渲染');
  const [count, setCount] = useState(1);
  const [num, setNum] = useState(1);

  // 普通函数-每次都会重新创建,如果传递给子组件,意味着子组件的props永远是不同的,这是memo对性能的优化永远不会生效
  // const fun = () => {
  //   setCount(prevState => prevState + 1)
  // }
  // temp ? console.log(temp === fun) : temp = fun

  // 在依赖不变的情况下,多次定义时,返回的值是相同的
  const addCount = useCallback(() => {
    setCount(prevState => prevState + num);
  }, [count]);
  temp ? console.log(temp === addCount) : temp = addCount

  // 没有依赖项时,
  const addNum = useCallback(() => {
    // console.log('addNum')
    setNum(prevState => prevState + 1);
  }, []);

  return (
    <div>
      <h2>App Count-- {count}</h2>
      <h2>App Num-- {num}</h2>
      <button onClick={addCount}>增加Count</button>
      <button onClick={addNum}>增加Num</button>
      <div style={{ padding: '14px', border: '1px solid black' }}>
        <A onAdd={addNum} />
      </div>
    </div>
  )
}

export default App

useMemo

useMemo 在每次重新渲染的时候能够缓存计算的结果,多次定义时,返回的值是相同的。这种缓存返回值的方式叫做记忆化(memoization)

const cachedValue = useMemo(calculateValue, dependencies)

参数一:要缓存计算值的函数,它应该是一个可以返回任意类型且没有参数的纯函数。在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值,不再重新计算。否则,会再次调用 calculateValue函数计算最新结果并返回,然后缓存该结果以便下次重复使用。

参数二:所有在 calculateValue 函数中使用的响应式变量( props、state 和直接在组件中定义的变量和函数)组成的数组。

用于性能优化的点:

  • 跳过代价昂贵的计算:对于没有必要每次渲染都需要重新进行大量计算而得到的数据,应该使用useMemo对计算结果进行缓存,直到依赖项发生变化。
  • 跳过组件的重新渲染:对子组件传递相同内容的对象时,使用useMemo进行性能的优化
import React, { memo, useCallback } from 'react'
import { useMemo, useState } from 'react'

const HelloWorld = memo(function (props) {
  console.log("HelloWorld被渲染")
  return <h2>Hello World</h2>
})

function calcNumTotal(num) {
  console.log("calcNumTotal的计算过程被调用")
  let total = 0
  for (let i = 1; i <= num; i++) {
    total += i
  }
  return total
}

const App = memo(() => {
  const [count, setCount] = useState(0)

  // 每次都会执行
  // const result = calcNumTotal(50)

  // 不依赖任何的值, 进行计算
  const result = useMemo(() => {
    return calcNumTotal(50)
  }, [])

  // 依赖count
  // const result = useMemo(() => {
  //   return calcNumTotal(count*2)
  // }, [count])

  // 使用useMemo对子组件渲染进行优化
  // const info = { name: "why", age: 18 }
  const info = useMemo(() => ({ name: "why", age: 18 }), [])

  // useMemo和useCallback的对比,
  // useMemo返回函数时和useCallback效果相同,但是更应该使用useCallback
  // function fn() { }
  // const increment = useCallback(fn, [])
  // const increment2 = useMemo(() => fn, [])
  // console.log(increment === increment2)

  return (
    <div>
      <h2>计算结果: {result}</h2>
      <h2>计数器: {count}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>

      <HelloWorld result={result} info={info} />
    </div>
  )
})

export default App

useRef

const ref = useRef(initialValue)

useRef 返回一个只包含一个current属性的ref对象,并且在后续的渲染中返回的 ref对象 是相同的。

它有两种用法:

  • 在函数组件中获取真实dom元素对象 或者 组件(class组件)实例
  • 使用ref对象的current属性保存数据,改变current属性不会触发重新渲染
import { PureComponent, useEffect, useRef } from 'react'

class Home extends PureComponent {
  sayHi = () => {
    console.log('say hi')
  }
  render() {
    return <div>Home</div>
  }
}

function App() {
  // 获取dom和class组件的实例
  const h1Ref = useRef(null)
  const homeRef = useRef(null)

  useEffect(() => {
    console.log('h1Ref:', h1Ref) 
    console.log('homeRef', homeRef) 
    homeRef.current.sayHi()
  }, [])

  return (
    <div>
      <h1 ref={h1Ref}>this is h1</h1>
      <Home ref={homeRef}/>
    </div>
  )
}
export default App

useImperativeHandle

在使用forwardRef对 ref 进行转发时,子组件拿到父组件传递过来的ref,绑定到自己内部的元素上。这时父组件拿到的是完整的DOM对象,父组件可以进行任意的操作,这可能会导致某些不可控的情况。

useImperativeHandle允许你自定义暴露给父组件的对象,该对象包含你想暴露的方法。

useImperativeHandle(ref,createHandle,dependencies?)

参数 :

  • ref:该 ref 是你从 forwardRef 渲染函数 中获得的第二个参数。
  • createHandle:该函数无需参数,它返回你想要暴露的 ref 的句柄。该句柄可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。
  • 可选的dependencies:函数 createHandle 代码中所用到的所有反应式的值的列表。反应式的值包含 props、状态和其他所有直接在你组件体内声明的变量和函数。
import React, { memo, useRef, forwardRef, useImperativeHandle } from 'react'

const HelloWorld = memo(forwardRef((props, ref) => {
  const inputRef = useRef()

  // 子组件对父组件传入的ref进行处理
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus()
      },
      setValue(value) {
        inputRef.current.value = value
      }
    }
  })

  return <input type="text" ref={inputRef} />
}))


const App = memo(() => {
  const titleRef = useRef()
  const inputRef = useRef()

  function handleDOM() {
    console.log(inputRef.current)
    inputRef.current.focus()
    inputRef.current.setValue("哈哈哈")
  }

  return (
    <div>
      <h2 ref={titleRef}>哈哈哈</h2>
      <HelloWorld ref={inputRef} />
      <button onClick={handleDOM}>DOM操作</button>
    </div>
  )
})

export default App

useLayoutEffect

useLayoutEffectuseEffect的一个版本。

  • useEffect是在浏览器重新绘制屏幕之后执行,不会阻塞DOM的是更新。
  • useLayoutEffect是在浏览器重新绘制屏幕之前执行,会阻塞DOM的是更新。
  • useLayoutEffect可能会影响性能,应该尽可能的使用useEffect
import React, { memo, useEffect, useLayoutEffect, useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  // 可以明显看到在useEffect中更改样式时出现了闪烁的现象
  // useEffect(() => {
  //   console.log("useEffect")
  //   if (count === 0) {
  //     setCount(Math.random() * 100)
  //   }
  // })

  // 在浏览器重新绘制屏幕之前更改样式不会出现闪烁
  useLayoutEffect(() => {
    console.log("useLayoutEffect")
    if (count === 0) {
      setCount(Math.random() * 100)
    }
  })

  console.log("App render")

  return (
    <div>
      <h2 style={{lineHeight: count + 'px'}}>count: {count}</h2>
      <button onClick={e => setCount(0)}>设置为0</button>
    </div>
  )
})

export default App

useDebugValue

为自定义 Hook 添加标签,标签会在 React 开发工具 中显示。

useDebugValue(value,format?)

4_React

useId

调用useId后生成一个唯一的ID,id不会因为组件的重新渲染而重新生成,适用于需要唯一id的场景,但不适用于列表的key。

const id = useId()

useId的主要作用是保证应用程序在服务端和客户端生成的ID是唯一的,从而避免通过其他手段生成的id不一致,而导致 hydration mismatch

import React, { memo, useId, useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  const id = useId()
  const id2 = useId()
  console.log(id, id2)

  return (
    <div>
      <label htmlFor={id}>
        用户名:<input id={id} type="text"/>
      </label>
      <h3>count:{count}</h3>
      <button onClick={e => setCount(count+1)}>count+1</button>
    </div>
  )
})

export default App

useTransition

useTransition可以在不阻塞UI的情况下更新状态,所以只有在可以访问该状态的set函数时才能使用。如果你想响应某个 prop 或自定义 Hook 值启动转换,请尝试使用 useDeferredValue.

const [isPending, startTransition] = useTransition()

useTransition返回一个pending状态和一个启动过渡任务的函数。传递给startTransition的回调函数必须是同步的。

在下面的例子中:

  • 将浏览器性能中的GPU进行6倍降速

4_React

import React, { memo, useState, useTransition } from 'react'

const Home = () => {
  return (
    <h2>Home Page</h2>
  )
}

const Find = () => {
  console.log('Find')

  let lis = []
  for (let i = 0; i < 5000; i++) {
    lis.push(<li key={i} >{i}</li>)
  }

  return (
    <div>
      <h2>Find Page</h2>
      <ul>
        {lis}
      </ul>
    </div>
  )
}

const My = () => {
  return (
    <h2>My Page</h2>
  )
}

const TabBtn = (props) => {
  const { children, isActive, onClick } = props
  const [isPending, startTransition] = useTransition()

  if (isActive) {
    return <span style={{ color: '#409eff' }}>{children}</span>
  }

  if (isPending) {
    return <span style={{ color: 'gray' }}>{children}...</span>
  }

  return (
    <button
      onClick={() =>
        startTransition(() => {
          onClick()
        })
      }
      >
      {children}
    </button>
  )
}

const App = memo(() => {
  const [tab, setTab] = useState('Home')

  return (
    <div>
      <div>
        <TabBtn isActive={tab === 'Home'} onClick={() => setTab('Home')}>Home</TabBtn>
        <TabBtn isActive={tab === 'Find'} onClick={() => setTab('Find')}>Find</TabBtn>
        <TabBtn isActive={tab === 'My'} onClick={() => setTab('My')}>My</TabBtn>
      </div>
      {tab === 'Home' && <Home />}
      {tab === 'Find' && <Find />}
      {tab === 'My' && <My />}
    </div>
  )
})

export default App

useDeferredValue

const deferredValue = useDeferredValue(value)

useDeferredValue接收一个原始值(如字符串或者数字)或在渲染之外创建的对象(渲染期间创建的对象每次都不同,会导致不必要的重新渲染),返回该值的延迟版本。

在组件更新时会先尝试使用旧值进行重新渲染,再在后台使用新值进行另一个重新渲染。这个后台渲染在 value 发生了变化时会中断,如果value变化的速度比重新渲染的速度快,那么组件只会在value不再变化时使用最新的值重新渲染,这个过程中会一直显示旧内容。

必须和React.memo一起使用才能有效。适用于状态的快速变化且访问不到该状态的set函数时,只对最后一次的渲染结果进行低优先级渲染。

在下面的例子中:

  • 将浏览器性能中的GPU进行6倍降速

4_React

  • 如果使用text传递到Item组件,在输入框连续输入123456789,Item组件中会打印 item 9000次,且输入框的内容会在渲染完后才显示。
  • 如果使用deferredText传递到Item组件,在输入框连续输入123456789,Item组件打印 item 的次数明显变少,且输入框的输入过程不会被阻塞。
    • Suspense的fallback效果只会在懒加载Item组件时出现。后续在新内容加载期间会显示旧内容。
    • 可以在新值和旧值不一样时,改变列表的样式,提示用户内容已过期。
    • useDeferredValue 引起的后台重新渲染在提交到屏幕之前不会触发 Effect。如果后台重新渲染被暂停,Effect 将在数据加载后和 UI 更新后运行。
import { memo } from "react"

const Item = memo((props) => {
  console.log('item')
  const { text } = props

  useEffect(() => {
    console.log('useEffect')
  })

  return (
    <li>{text}</li>
  )
})

export default Item
import React, { memo, useDeferredValue, useState, useTransition, Suspense } from 'react'
const Item  = React.lazy(() => import('./Item'))

const App = memo(() => {
  const [text, setText] = useState('')
  const deferredText = useDeferredValue(text)

  let items = []
  for (let i = 0; i < 1000; i++) {
    items.push(<Item key={i} text={deferredText} />)
  }

  return (
    <div>
      <input type="text" onInput={(e) => setText(e.target.value)} />
      <h2>列表: </h2>
      <ul style={{color: text !== deferredText ? 'gray' : 'black'}}>
        <Suspense fallback={<h2>...loading</h2>}>
          {items}
        </Suspense>
      </ul>
    </div>
  )
})

export default App

自定义Hook

自定义Hook其实只是一个普通的函数,严格意义上来说它并不算React的特性。

自定义Hook的名字需要使用 use 开头,如 useWindowScrolluseFetch

在自定义Hook中可以使用React自带的 Hook,方便我们对函数代码逻辑进行抽取。

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

// lcoalStorage的存取
function useLocalStorage(key) {
  // 从localStorage中获取指定key的数据, 根据该数据创建组件的state
  const [data, setData] = useState(() => JSON.parse(localStorage.getItem(key)) || '')

  // data改变时将最近的值存储到localStorage
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(data))
  }, [data])

  // 将data和setData返回给组件, 让组件可以使用和修改值
  return [data, setData]
}

// 获取页面滚动位置
function useScrollPosition() {
  const [scrollX, setScrollX] = useState(0)
  const [scrollY, setScrollY] = useState(0)

  useEffect(() => {
    function handleScroll() {
      // console.log(window.scrollX, window.scrollY)
      setScrollX(window.scrollX)
      setScrollY(window.scrollY)
    }
    window.addEventListener("scroll", handleScroll)

    return () => {
      window.removeEventListener("scroll", handleScroll)
    }
  }, [])

  return [scrollX, scrollY]
}

// 生命周期
function useLifeCycleLog(cpnName) {
  useEffect(() => {
    console.log(cpnName + "组件被创建")
    return () => {
      console.log(cpnName + "组件被销毁")
    }
  }, [])
}

const Home = memo(() => {
  useLifeCycleLog('Home')

  return (
    <div>Home Page</div>
  )
})

const App = memo(() => {
  const [token, setToken] = useLocalStorage('token')
  const [scrollX, scrollY] = useScrollPosition()

  const [isShowHome, setIsShowHome] = useState(true)

  return (
    <div style={{ width: '2000px', height: '2000px' }}>
      <h1>App:</h1>
      <div>
        token: {token}
        <button onClick={() => setToken(Math.random())}>设置token</button>
      </div>
      <div>scrollX - {scrollX} , scrollY - {scrollY}</div>

      <div style={{padding: '20px', border: '1px solid green'}}>
        <button onClick={() => setIsShowHome(val => !val)}>切换</button>
        {isShowHome && <Home />}
      </div>
    </div>
  )
})

export default App

原文链接:https://juejin.cn/post/7265188228565745722 作者:YCL_

(0)
上一篇 2023年8月10日 上午10:16
下一篇 2023年8月10日 上午10:26

相关推荐

发表回复

登录后才能评论