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相关的问题。
Hook使用注意事项
- 只能在函数组件和自定义hook中使用,不能在类组件,或者函数组件之外的地方使用;
- 只能在函数的最外层调用,不能在循环、条件判断、或者子函数中调用
useState
useState需要从react中导入,能够为函数组件提供状态(state)。
可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。
- 参数:初始化值,只会在组件第一次渲染时生效 , 不设置则为undefined;
-
- 如果初始state需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用
- 返回值:数组,包含两个元素
-
- 元素一:当前状态的值
- 元素二:修改状态值的函数。设置新的状态后组件会重新渲染,根据新的值返回DOM结构。
注意事项
- useState的初始值(参数)只会在组件第一次渲染时生效 。 也就是说,以后的每次渲染,useState获取到都是最新的状态值,React组件会记住每次最新的状态值
- 修改状态的时候,一定要使用新得状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
javascript
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)执行
javascript
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
基础使用
javascript
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发生变化时,才会重新执行(依赖于谁)。
- 不添加依赖项(默认): 组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
- 添加空数组: 组件只在首次渲染时执行一次
- 添加特定依赖项: 副作用函数在首次渲染时和在依赖项发生变化时执行
注意事项:
useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
javascript
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副作用函数执行之前)和卸载时自动执行。
javascript
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,因为异步会导致清理函数无法立即返回
scss
// 错误做法
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的值。
javascript
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中返回的函数,让使用者在需要时能够优化自己的代码
javascript
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进行性能的优化
javascript
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属性不会触发重新渲染
javascript
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、状态和其他所有直接在你组件体内声明的变量和函数。
javascript
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
useLayoutEffect
是useEffect
的一个版本。
useEffect
是在浏览器重新绘制屏幕之后执行,不会阻塞DOM的是更新。useLayoutEffect
是在浏览器重新绘制屏幕之前执行,会阻塞DOM的是更新。useLayoutEffect
可能会影响性能,应该尽可能的使用useEffect
javascript
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?)
useId
调用useId
后生成一个唯一的ID,id不会因为组件的重新渲染而重新生成,适用于需要唯一id的场景,但不适用于列表的key。
const id = useId()
useId
的主要作用是保证应用程序在服务端和客户端生成的ID是唯一的,从而避免通过其他手段生成的id不一致,而导致 hydration mismatch
javascript
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倍降速
javascript
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倍降速
- 如果使用text传递到
Item
组件,在输入框连续输入123456789,Item
组件中会打印 item 9000次,且输入框的内容会在渲染完后才显示。 - 如果使用deferredText传递到Item组件,在输入框连续输入123456789,
Item
组件打印 item 的次数明显变少,且输入框的输入过程不会被阻塞。
-
Suspense
的fallback效果只会在懒加载Item
组件时出现。后续在新内容加载期间会显示旧内容。- 可以在新值和旧值不一样时,改变列表的样式,提示用户内容已过期。
useDeferredValue
引起的后台重新渲染在提交到屏幕之前不会触发 Effect。如果后台重新渲染被暂停,Effect 将在数据加载后和 UI 更新后运行。
javascript
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
javascript
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 开头,如 useWindowScroll
、useFetch
。
在自定义Hook中可以使用React自带的 Hook,方便我们对函数代码逻辑进行抽取。
javascript
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