React.js 进阶本文介绍了 React 的关键概念,包括 useEffect 和 useMemo 的用法、useC
本文介绍了 React 的关键概念,包括 useEffect 和 useMemo 的用法、useCallback 的性能优化、ref 的使用、以及高阶组件(HOC)的应用以及 React 的生命周期。
useEffect
✅useEffect 是 React 的 Hook,用于处理副作用,如数据获取、DOM 操作和订阅,它允许在函数组件中执行副作用代码,并控制副作用的执行时机。
基本语法
函数签名 useEffect(()=>destroy, deps)
-
cb: ()=> destroy 第一个参数是一个回调函数
- destroy 作为 callback 的返回值会在写一次执行之前调用, 用于清除上一次 callback 带来的副作用
- deps 第二个参数, 是一个数组, 数组中的值改变会执行 callback 返回的 destroy 然后再一次执行 callback 函数
import React, { useEffect, useState } from 'react'
const getBookList = () =>
new Promise((resolve) => {
setTimeout(() => {
resolve(['从零开始学前端', '一小时精通 JavaScript', 'JS 从入门到放弃'])
}, 1000)
})
function LifeCycle(props) {
const [list, setList] = useState([])
useEffect(() => {
getBookList().then((res) => {
setList((list) => res)
})
}, [])
return (
<div>
<ul>
{list.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
)
}
export default LifeCycle
useEffect 模拟生命周期的?
函数组件生命周期替代方案之 useEffect 是如何模拟生命周期的?
deps 为空数组时: 只执行一次
-
componentDidMount
- 场景: 请求数据, 注册事件监听, 操作 DOM, 定时器。ComponentDidMount 相当于 Vue 中的 Mounted
-
componentWillUnmount 相当于 Vue 的 beforeDestory
- 场景: 可以用于清理副作用、取消订阅或释放资源等操作
// 空数组只执行一次
// 应用场景: 请求数据,注册事件监听,操作DOM,定时器。
useEffect ( () => { console . log ( 'componentDidMount' ) return () => { console . log ( 'componentWillUnmount' ) // 切记 useEffect 必须依赖于空数组 } }, [])
-
componentWillReceiveProps
- 监听父组件传递的 props 变化
// props 变化的时候做一个执行, 当我接收到 props 变化的时候执行的生命周期
// componentWillReceiveProps
useEffect(() => {
console.log('componentWillReceiveProps')
}, [props])
-
componentDidUpdate
- 引起组件渲染、数据变化就会执行
useEffect ( () => {
// 不管是 props 还是 state 引起的组件渲染这里都执行
console . log ( 'componentDidUpdate' )
})
- getDerivedStateFromProps
会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用
getDerivedStateFromProps 在每次更新(即使是在首次挂载时)之前调用
const [flag, setFlag] = useState(() => {
console.log(
`getDerivedStateFromProps: 会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用`
)
return false
})
useMemo
useMemo 是 React 的一个 Hook,用于优化性能。它可以缓存计算结果,避免在每次渲染时都重新计算,尤其是在组件中涉及复杂计算时。useMemo 的主要作用是优化性能,而不是管理状态。
函数签名
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- useMemo:函数名。
- computeExpensiveValue:计算值的函数,通常是昂贵的计算操作。
- [a, b] :依赖数组,表示在这些值变化时,computeExpensiveValue 函数才会重新执行。
- memoizedValue:缓存的计算结果。
应用场景
- 性能优化:避免不必要的重新计算。例如,当计算结果依赖于大量数据或复杂逻辑时,可以使用 useMemo 来减少计算次数。
- 避免不必要的渲染:在 React.memo 或 PureComponent 中使用 useMemo 以确保组件只在必要时重新渲染。
优化复杂计算
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ a, b }) {
// 计算昂贵的值
const computeExpensiveValue = (a, b) => {
console.log('Computing expensive value...');
return a * b; // 假设这是一个昂贵的计算
};
// useMemo 用于缓存计算结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return (
<div>
<p>Computed Value: {memoizedValue}</p>
</div>
);
}
export default ExpensiveComponent;
避免不必要的渲染
import React, { useState, useMemo } from 'react';
function List({ items }) {
// 计算排序后的列表
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return items.slice().sort();
}, [items]);
return (
<ul>
{sortedItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}
function App() {
const [items, setItems] = useState(['Banana', 'Apple', 'Orange']);
return (
<div>
<List items={items} />
<button onClick={() => setItems([...items, 'Grape'])}>Add Grape</button>
</div>
);
}
export default App;
useCallback
useCallback 是 React 的一个 Hook,用于优化性能。它用于缓存函数实例,避免在组件每次重新渲染时创建新的函数实例。适用于需要将函数作为 props 传递给子组件,且希望避免子组件因函数引用变化而重新渲染的情况。
函数签名
const memoizedCallback = useCallback(() => {
// 需要缓存的函数体
}, [dependencies]);
- useCallback:函数名,用于创建缓存的回调函数。
- () => {} :函数体,定义需要缓存的逻辑。
- [dependencies] :依赖数组,表示当依赖项变化时,缓存的函数才会被重新创建。依赖项为空数组 [] 表示函数只在初次渲染时创建一次。
应用场景
- 避免子组件不必要的重新渲染:当函数作为 prop 传递给子组件时,useCallback 可以帮助保持函数引用不变,从而避免子组件不必要的重新渲染。
- 缓存函数实例:在性能优化场景中,通过 useCallback 缓存函数实例,减少函数的重新创建。
import React, { useState, useCallback } from 'react';
function ChildComponent({ onClick }) {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click Me</button>;
}
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数实例
const handleClick = useCallback(() => {
alert('Button clicked');
}, []); // 依赖项为空数组,函数只在初次渲染时创建一次
return (
<div>
<ChildComponent onClick={handleClick} />
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default ParentComponent;
Ref
ref 是 React 提供的一个功能,用于访问和操作 DOM 元素或组件实例。它允许你在函数组件和类组件中直接获取对真实 DOM 节点的引用,或者访问类组件的实例方法和属性。也可以用来存储全局变量。
在类组件中使用 createRef
import React, { Component, createRef } from 'react'
class ClassRef extends Component {
constructor(props) {
super(props)
this.inputRef = createRef()
this.numberRef = createRef()
this.numberRef.current = 0
}
handleClick = () => {
this.inputRef.current.focus()
console.log(this.inputRef)
this.numberRef.current = ++this.numberRef.current
console.log(this.numberRef)
}
render() {
return (
<div>
<div>this is class ref </div>
<input type="text" ref={this.inputRef} />
{/*期望点击 focus 按钮, input 设置为聚焦状态*/}
<button onClick={this.handleClick}>focus</button>
</div>
)
}
}
export default ClassRef
在函数组件中使用
import React, { useRef } from 'react'
function FuncRef(props) {
const inputRef = useRef()
return (
<div>
<h2>Func Ref</h2>
<input type="text" ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus Input</button>
</div>
)
}
export default FuncRef
Ref 常见的使用方法
- Ref 转发
- forwardRef
- useImperativeHandle
组件中的方法和值如何暴露给父组件使用?
- 父组件定义一个 ref
- 子组件接收 props 和 ref
- 子组件定义 ref 挂载到 dom
- 子组件定义 方法 通过 useImperativeHandle(ref, ()=({})) 暴漏
- 使用 ForwardRef 包装组件
- 父组件调用 ForwardRef 并将 ref 传入。
import React, { forwardRef, useImperativeHandle, useRef } from 'react'
export default function FRef(props) {
const exposeRef = useRef(null)
return (
<div>
<FancyInputRef ref={exposeRef} />
<button onClick={() => exposeRef.current?.focus()}>focus</button>
<button onClick={() => exposeRef.current?.changeValue(Math.random())}>
change
</button>
</div>
)
}
const MyInput = (props, ref) => {
const inputRef = useRef()
const focus = () => {
inputRef.current?.focus()
}
const changeValue = (value) => {
inputRef.current.value = value
}
// 将方法暴露给外部
useImperativeHandle(ref, () => ({
focus,
changeValue,
}))
return (
<div>
<div>----- 我是一个 Input 组件 -----</div>
<input ref={inputRef} type="text" />
<div>----- ***************** -----</div>
</div>
)
}
const FancyInputRef = forwardRef(MyInput)
Context 上下文
类似于 Vue 中的 Provide 和 Inject
总结一下 React 中 Context 的概念和用法。
两种场景下的用法: 类组件和函数组件。
类组件中的 Context
- const ThemeContext = createContext(null)
- <ThemeContext.Provider value={}>
- <ThemeContext.Consumer> {(theme)=> }
函数组件中的 Context,
- const navContext = createContext(null)
- <NavContext.Provider value={}>
- const history = useContext(NavContext) 不需要再 Consumer了
类组件中 context 的用法
声明 context
import { createContext } from 'react'
export const ThemeContext = createContext('light')
使用 context: Provider / Consumer
import React, { Component } from 'react'
import { ThemeContext } from './ThemeContext'
class ClassContext extends Component {
constructor(props) {
super(props)
this.state = {
theme: 'light',
}
}
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<Parent />
<button onClick={() => this.setState({ theme: 'light' })}>Light</button>
<button onClick={() => this.setState({ theme: 'dark' })}>Dark</button>
</ThemeContext.Provider>
)
}
}
const Parent = (props) => (
<div>
<Child1></Child1>
<Child2></Child2>
</div>
)
class Child1 extends Component {
// 方法1 通过静态变量去拿
static contextType = ThemeContext
render() {
return <div>child1 --- theme --- {this.context}</div>
}
}
class Child2 extends Component {
// 方法1 通过 consumer
render() {
return (
<ThemeContext.Consumer>
{(theme) => <div>child2 --- theme --- {theme}</div>}
</ThemeContext.Consumer>
)
}
}
export default ClassContext
函数组件中 context 的用法
应用案例: 将顶层 history 相关的内容传递给任意一个子组件上
封装函数 withRouter
- createContext
- <NavContext.Provider value={history}>
- const history = useContext(NavContext)
- <Component history={history} />
实现步骤:
封装函数 withRouter
使用 createContext 创建 NavContext
使用 <NavContext.Provider value={history}> 设置 值为 history
使用 useContext(NavContext) 导出 history
通过组件的属性 history={history} 传递到子组件
import React, { Component, createContext, useContext } from 'react'
// 简单实现一个高阶组件 WithRouter
// 我要把最顶层的一些 history 相关的内容传递到,任意一个子组件上
const NavContext = createContext(null)
const history = window.history
export default function FuncContext(props) {
return (
<NavContext.Provider value={history}>
<Parent />
</NavContext.Provider>
)
}
function Parent() {
return (
<div>
<WithRouterChild />
</div>
)
}
const withRouter = (Component) => {
return () => {
const history = useContext(NavContext)
return <Component history={history} />
}
}
const Child = (props) => {
const handleClick = () => {
console.log(props.history)
props.history?.pushState({}, undefined, 'hello')
}
return (
<div>
<span>func usage</span>
<button onClick={handleClick}>route to hello</button>
</div>
)
}
const WithRouterChild = withRouter(Child)
高阶组件(HOC)
HOC 是一种用于增强组件的技术模式。它是一个函数,接受一个组件并返回一个新的组件,该新组件对原组件进行了扩展或增强。HOC 通常用于复用组件逻辑、注入额外的 props 或处理组件状态等。下面介绍两种 HOC 的应用。
属性代理
下面这段代码展示了如何使用属性代理(Higher-Order Component, HOC)来增强 React 组件。withCard 函数是一个高阶组件,接受一个颜色参数并返回一个新的组件,这个新组件为目标组件 Property 提供了额外的样式和属性。PropsProxy 组件展示了如何将 HocProperty 组件嵌入其中,从而应用装饰样式。
import React, { Component } from 'react'
export default function PropsProxy(props) {
return (
<div>
<HocProperty name={'hoc-property'} />
</div>
)
}
function Property({ name, hocStyle }) {
return (
<div>
我是一个普普通通的组件---{name}--{JSON.stringify(hocStyle)}
</div>
)
}
// 装饰组件的能力
// 柯里化进行扩展
const withCard = (color) => (Component) => {
return (props) => {
const hocStyle = {
margin: '5px',
background: 'rgba(0,0,0,0.5)',
color: color,
}
return (
<div style={hocStyle}>
-----------
<Component hocStyle={hocStyle} {...props} />
-----------
</div>
)
}
}
const HocProperty = withCard('red')(Property)
反向继承
下面这段代码展示了如何使用高阶组件(HOC)来优雅地实现组件加载时的埋点记录。logProps 函数创建了一个高阶组件,它接受一个 logMap 对象,该对象映射了组件元素的 ID 到日志事件。高阶组件通过反向继承,将原组件 Index 进行包装,并在 componentDidMount 生命周期方法中插入逻辑,用于记录特定元素的加载事件。这种方式优雅地将埋点逻辑与组件逻辑分离,使得代码更加可维护和复用。
import React, { Component } from 'react'
/**
场景: 我有一个组件,我们要非常优雅地实现,组件加载的埋点
* text btn
* text -> sendLog: my_text_show, id: my_text 文字加载时触发一个埋点事件
* btn -> sendLog: my_btn_show, id: my_btn
* 期望 高阶函数 LogProps({my_text:"my_text_show", my_btn:"my_btn_show"})(Index)
*/
export default function Extends(props) {
return (
<div>
<LogIndex />
</div>
)
}
function logProps(logMap) {
// wrappedComponent 相当于 Index
return (wrappedComponent) => {
// 最后要返回的组件
// 有可能事件曝光可能是在 ComponentDidMount 的时候发生
// 我要去劫持你的生命周期
// willMount -- beforeMount
// render
// didMount -- mounted
const didMount = wrappedComponent.prototype.componentDidMount
return class A extends wrappedComponent {
componentDidMount() {
if (didMount) {
didMount.apply(this)
}
Object.entries(logMap).forEach(([k, v]) => {
if (document.getElementById(k)) {
console.log('事件曝光', v)
}
})
}
render() {
return super.render()
}
}
}
}
class Index extends Component {
render() {
return (
<div>
<div id={'my_text'}>这是文字</div>
<button id={'my_btn'}>这是按钮</button>
</div>
)
}
}
const LogIndex = logProps({
my_text: 'my_text_show',
my_btn: 'my_btn_show',
})(Index)
React 生命周期总结
-
初始化阶段
-
constructor
-
getDerivedStateFromProps
-
componentWillMount
- getDerivedStateFromProps 存在 , componentWillMount 不执行。
-
render
-
compnentDidMount
-
更新阶段
-
componentWillReceiveProps
- getDerivedStateFromProps 存在 , componentWillReceiveProps 不执行。
-
getDerivedStateFromProps
-
shouldComponentUpdate
- 返回 boolean, 决定组件要不要执行
-
componentWillUpdate
-
render
-
getSnapshotBeforeUpdate 更新前的快照
-
componentDidUpdaet
-
销毁阶段
- componentWillUnmount
总结
本文详细介绍了 React 的几个核心概念和功能,包括 useEffect 用于处理副作用和模拟生命周期方法,useMemo 和 useCallback 用于性能优化,ref 用于直接操作 DOM 元素和组件实例,及其在类组件和函数组件中的应用。还讨论了 Context 的使用方式,介绍了高阶组件(HOC)如何通过属性代理和反向继承实现组件增强和埋点记录,最后总结了 React 组件的生命周期阶段。
转载自:https://juejin.cn/post/7402062432618807359