React 18笔记
一、临时运行一个项目
npx create-react-app 项目名称 // 安装的是最新版
二、jsx渲染
1、条件渲染
Test1.jsx
import React, { useState } from "react";function Test1() {let [objA, setA] = useState({a: 123,});let [show, changeShow] = useState(true);setTimeout(() => {setA({a: 666,});});console.log("render之前执行");return (<div>{show && <p>{objA.a}</p>}<p><button onClick={() => changeShow(!show)}>显/隐</button></p></div>);
}export default Test1;
2、循环渲染
Test1.jsx
import React, { useState } from "react";function Test1() {let [arr, setArr] = useState([{title: "title1",content: "content1"},{title: "title2",content: "content2"},{title: "title3",content: "content3"}])function createArrList(){let _arr = [];arr.forEach(item => {const _item = <div key={item.title}><h1>{item.title}</h1><p>{item.content}</p></div>;_arr.push(_item);});return _arr;}return (<div>{createArrList()}</div>);
}export default Test1;
三、事件绑定
import React, { useState } from "react";function Test1() {let [show, setShow] = useState(false);return (<div>{show && <h1>React Test</h1>}<button onClick={() => setShow(!show)}>{show ? "隐藏" : "显示"}</button></div>);
}export default Test1;
四、添加样式:
1、内联样式
import React from "react";function Test1() {return (<div style={{fontSize: "30px"}}>React Test</div>);
}export default Test1;
2、class定义:
test1.css
.fontStyle{font-size: 36px;
}
Test1.jsx
import React from "react";
import "./test1.css"function Test1() {return (<div className="fontStyle">React Test</div>);
}export default Test1;
3、仅当前组件生效(相当于Vue中的scope)
Test1.jsx同级目录中定义“module.css”结尾的css文件:test1.module.css
.fontStyle{font-size: 36px;
}
Test1.jsx中使用:
import React from "react";
import style from "./test1.module.css"function Test1() {return (<div className={style["fontStyle"]}>React Test</div>);
}export default Test1;
五、生命周期
1、类组件中
import React from "react";class Test2 extends React.Component {constructor(prop) {super(prop);this.state = {name: "React",};}render() {console.log("render");return (<div><h1>{this.state.name}</h1><p><button onClick={() => this.setState({name: "Vue"})}>Vue</button></p></div>);}componentDidMount() {console.log("componentDidMount");}shouldComponentUpdate() {console.log("shouldComponentUpdate");return true;}componentDidUpdate() {console.log("componentDidUpdate");}
}export default Test2;
首次渲染时执行结果如下:
render
componentDidMount
当点击按钮时,如果shouldComponentUpdate返回true,执行结果如下:
shouldComponentUpdate
render
componentDidUpdate
如果shouldComponentUpdate返回false,执行结果如下:
shouldComponentUpdate
如果有子组件:
Son.jsx
import React from "react";class Son extends React.Component {constructor(prop) {super(prop);this.state = {name: "Son1",};}render() {console.log("son render");return (<div><h1>{this.state.name}</h1><p><button onClick={() => this.setState({name: "Son2"})}>Son1</button></p></div>);}componentDidMount() {console.log("son componentDidMount");}shouldComponentUpdate() {console.log("son shouldComponentUpdate");return true;}componentDidUpdate() {console.log("son componentDidUpdate");}
}export default Son;
父组件:
Parent.jsx
import React from "react";
import Sonfrom "./Son.jsx"class Parent extends React.Component {constructor(prop) {super(prop);this.state = {name: "React",};}render() {console.log("render");return (<div><h1>{this.state.name}</h1><p><button onClick={() => this.setState({name: "Vue"})}>Vue</button></p><div style = {{border: "1px solid #000"}}><Son/></div></div>);}componentDidMount() {console.log("componentDidMount");}shouldComponentUpdate() {console.log("shouldComponentUpdate");return true;}componentDidUpdate() {console.log("componentDidUpdate");}
}export default Parent;
首次渲染时:
render
son render
son componentDidMount
componentDidMount
父组件中state更新:
shouldComponentUpdate
render
son shouldComponentUpdate
son render
son componentDidUpdate
componentDidUpdate
子组件中state更新:
son shouldComponentUpdate
son render
son componentDidUpdate
2、函数式组件中
从生命周期的角度考虑,函数式组件本身相当于类组件的render函数,useEffect钩子兼具类组件中componentDidMount(一定有)+componentDidUpdate(偶尔有)的功能,componentDidUpdate功能有没有取决于useEffect第二个参数,分三种情况:
1、useEffect第二个参数是一个空数组,则其无componentDidUpdate功能,任何一个state改变时,useEffect中回调函数都不会执行,此种情况useEffect回调函数只在组件第一次渲染时执行,即只有componentDidMount;
2、useEffect第二个参数是一个数组,且数组中有state变量,则其有componentDidUpdate功能:数组中state改变时,useEffect中回调函数会执行。此种情况兼具componentDidMount+数组中state的componentDidUpdate功能;
3、useEffect没有第二个参数,则其有componentDidUpdate功能:任何state改变时,useEffect中回调函数会执行。此种情况兼具componentDidMount+任何state的componentDidUpdate功能;
看演示:
useEffect第二个参数是一个空数组
import React, {useState, useEffect} from "react"function Test3(){let [a, setA] = useState(0);let [b, setB] = useState(1);useEffect(() => {console.log("useEffect");}, []);console.log("render");return (<div><h1>a: {a}</h1><h1>b: {b}</h1><p><button onClick={() => setA(++a)}>change a</button></p><p><button onClick={() => setB(++b)}>change b</button></p></div>)
}export default Test3;
首次加载时返回结果:
render
useEffect
点击任何一个按钮时返回结果
render
useEffect第二个参数是一个数组,且数组中有state变量
import React, {useState, useEffect} from "react"function Test3(){let [a, setA] = useState(0);let [b, setB] = useState(1);useEffect(() => {console.log("useEffect");}, [a]);console.log("render");return (<div><h1>a: {a}</h1><h1>b: {b}</h1><p><button onClick={() => setA(++a)}>change a</button></p><p><button onClick={() => setB(++b)}>change b</button></p></div>)
}export default Test3;
首次加载时返回结果:
render
useEffect
点击change a按钮时返回结果
render
useEffect
点击change b按钮时返回结果
render
useEffect没有第二个参数
import React, {useState, useEffect} from "react"function Test3(){let [a, setA] = useState(0);let [b, setB] = useState(1);useEffect(() => {console.log("useEffect");});console.log("render");return (<div><h1>a: {a}</h1><h1>b: {b}</h1><p><button onClick={() => setA(++a)}>change a</button></p><p><button onClick={() => setB(++b)}>change b</button></p></div>)
}export default Test3;
首次加载时返回结果:
render
useEffect
点击任何一个按钮时返回结果
render
useEffect
总结:
useEffect在首次渲染时其回调函数一定会执行,之后会不会执行取决于其第二个参数。
六、父子组件传值:
1、父传子
函数式组件
父组件:App.jsx
import React, {useState} from 'react';
import './App.css';
import Child from "./Child"function App() {let [num, setNum] = useState(0);return (<div className="App"><div>Parent: {num}</div><Child num={num} setNum={setNum}/></div>);
}export default App;
子组件:Child.jsx
import React, {useState, useEffect} from "react"function Child(props){let [myNum, setMyNum] = useState(props.num); // 通过props.num接收父组件传过来的numfunction changeNum(){const _myNum = ++myNum;setMyNum(_myNum);props.setNum(_myNum); // 如果_myNum为数组,则需要展开props.setNum([..._myNum])}return (<div><h1>myNum: {myNum}</h1><p><button onClick={changeNum}>change myNum</button></p></div>)
}export default Child;
子组件通过props.num接收父组件传过来的num。
类组件
子组件:Child.jsx
import React, { useState, useEffect } from "react";class Child extends React.Component {constructor(prop) {super(prop);this.state = {myNum: this.props.num,};// 手动绑定 thisthis.changeNum = this.changeNum.bind(this);}changeNum() {const _myNum = ++this.state.myNum;this.setState({myNum: _myNum});this.props.setNum(_myNum);}render() {return (<div><h1>myNum: {this.state.myNum}</h1><p><button onClick={this.changeNum}>change myNum</button></p></div>);}
}export default Child;
2、子传父
父组件:App.jsx
import React, {useState} from 'react';
import './App.css';
import Child from "./Child"function App() {let [num, setNum] = useState(0);return (<div className="App"><div>Parent: {num}</div><Child num={num} setNum={setNum}/></div>);
}export default App;
子组件:Child.jsx
import React, {useState, useEffect} from "react"function Child(props){let [myNum, setMyNum] = useState(props.num);function changeNum(){const _myNum = ++myNum;setMyNum(_myNum);props.setNum(_myNum); // 通过父组件传过来的函数setNum传递值给父组件}return (<div><h1>myNum: {myNum}</h1><p><button onClick={changeNum}>change myNum</button></p></div>)
}export default Child;
或
import React, { useState, useEffect } from "react";class Child extends React.Component {constructor(prop) {super(prop);this.state = {myNum: this.props.num,};// 手动绑定 thisthis.changeNum = this.changeNum.bind(this);}changeNum() {const _myNum = ++this.state.myNum;this.setState({myNum: _myNum});this.props.setNum(_myNum);}render() {return (<div><h1>myNum: {this.state.myNum}</h1><p><button onClick={this.changeNum}>change myNum</button></p></div>);}
}export default Child;
通过父组件传过来的函数setNum传递值给父组件。
七、React中操作DOM:
1、类组件中
使用React.createRef(),推荐
import React from "react";class Test extends React.Component {constructor(prop) {super(prop);this.myBox = React.createRef();}render() {return (<div><h1 ref={this.myBox}>myBox</h1><p><button onClick={() => console.log(this.myBox.current.innerText)}>获取myBox DOM</button></p></div>);}
}export default Test;
使用回调形式的 Ref(旧方式,依然可用)
import React from "react";class MyComponent extends React.Component {constructor(props) {super(props);this.inputElement = null; // 用于保存 DOM 引用}handleClick = () => {if (this.inputElement) {this.inputElement.focus();}};render() {return (<div>{/* 回调形式的 ref */}<inputref={(el) => (this.inputElement = el)}type="text"/><button onClick={this.handleClick}>聚焦输入框</button></div>);}
}
2、函数式组件中
使用 useRefHook
import React, { useRef, useEffect } from 'react';function Test() {// 1. 创建一个 refconst inputRef = useRef(null);// 2. 在 useEffect 或事件处理中操作 DOMuseEffect(() => {if (inputRef.current) {inputRef.current.focus(); // 操作 DOM:让输入框获得焦点console.log(inputRef.current.value); // 访问 DOM 属性}}, []);const handleClick = () => {if (inputRef.current) {inputRef.current.value = 'Hello, DOM!';inputRef.current.focus();}};return (<div>{/* 3. 将 ref 绑定到 DOM 元素 */}<input ref={inputRef} type="text" /><button onClick={handleClick}>设置值并聚焦</button></div>);
}export default Test;
八、React.memo
React.memo是 React 提供的一个 高阶组件(HOC),用于 优化函数组件的渲染性能。它的主要作用是 避免不必要的重新渲染。
作用
当一个函数组件的 props 没有发生变化时,React.memo会 阻止该组件重新渲染,从而提升性能。
换句话说:
React.memo
对函数组件做了浅比较(shallow comparison),如果传入的props
没有变化,就不会重新执行组件函数,也不会触发重新渲染。
适用场景
- 是 函数组件
- 渲染开销较大
- props 经常不变但父组件频繁渲染
在这些情况下,使用 React.memo可以避免子组件无意义的重复渲染,提高性能。
基本用法
import React from 'react';// 普通函数组件
const MyComponent = (props) => {console.log('MyComponent 被渲染了');return <div>{props.text}</div>;
};// 用 React.memo 包裹
export default React.memo(MyComponent);
综合使用
App.jsx
import React, {useState} from 'react';
import './App.css';
import Child from "./Child"function App() {let [num, setNum] = useState(0);let [num1, setNum1] = useState(0);console.log("App Render");return (<div className="App"><div>Parent num: {num} <button onClick={() => setNum(++num)}>change num</button></div><div>Parent num1: {num1} <button onClick={() => setNum1(++num1)}>change num1</button></div><Child num={num}/></div>);
}export default App;
Child.jsx
import React, { useState } from 'react';function Child(props) {console.log("Child Render");let [number, setNumber] = useState(0);return (<div><h1>React.memo使用</h1><p>props.num:{props.num}</p><p>number:{number} <button onClick={() => setNumber(++number)}>change number</button></p></div>);
}export default React.memo(Child);
Child组件,只有当父组件props.num更新,或者自身state更新时Child组件才会重新渲染。
注意事项
- 只适用于函数组件,类组件有类似的优化机制:shouldComponentUpdate
- 仅对 props 做优化,如果组件自身状态(state)改变,仍然会重新渲染
- 不要滥用:不是所有组件都适合用 React.memo,只有真正遇到性能瓶颈时才使用
- 浅比较有局限:对象或数组等引用类型即使内容一样,但引用不同也会导致重新渲染
与 React.PureComponent的区别
特性 | React.memo | React.PureComponent |
---|---|---|
适用组件类型 | 函数组件 | 类组件 |
作用 | 避免函数组件不必要的渲染 | 避免类组件不必要的渲染 |
比较方式 | 默认浅比较 props | 默认浅比较 props 和 state |
自定义比较逻辑 | 第二个参数 areEqual | 重写 shouldComponentUpdate |
总结
项目 | 说明 |
---|---|
是什么 | 用于函数组件的高阶组件,优化渲染性能 |
作用 | 当组件的 props 没有变化时,阻止重新渲染 |
适用场景 | 渲染成本高、频繁渲染但 props 不常变化的组件 |
如何使用 |
|
自定义比较 | 可传入第二个参数函数进行自定义比较逻辑 |
注意事项 | 不要滥用,只对 props 做浅比较,不适用于状态变化 |
九、React.PureComponent
React.PureComponent是一个类组件基类,类似于 React.Component,但它自动为 props和 state实现了浅比较的 shouldComponentUpdate。如果组件的 props和 state没有发生浅层变化(即引用未变),它就不会重新渲染,从而提升性能。
注意:它只进行浅比较,如果你的
props
或state
是嵌套对象或数组,修改内部属性但保持引用不变时,PureComponent
不会检测到变化,也就不会触发更新。
React 18 中使用 React.PureComponent
基本用法
import React, { useState } from 'react';class Child extends React.PureComponent{render(){console.log("PureComponent类组件被渲染了");return(<div><h1>PureComponent 示例</h1><p>Count: {this.props.count}</p><p>Name: {this.props.name}</p></div>)}
}export default Child;
然后在父组件中使用它:
import React, {useState} from 'react';
import './App.css';
import Child from "./Child"function App() {let [num, setNum] = useState(0);let [count, setCount] = useState(0);let [name, setName] = useState("React");console.log("App Render");return (<div className="App"><div>Parent num: {num} <button onClick={() => setNum(++num)}>change num</button></div><div>Parent count: {count} <button onClick={() => setCount(++count)}>change count</button></div><div>Parent name: {count} <button onClick={() => setName(name+"-")}>change name</button></div><Child count={count} name={name}/></div>);
}export default App;
观察:
当你点击“change count”,count变了,MyPureComponent会重新渲染。
当你点击“change name”,name变了,MyPureComponent会重新渲染。
如果你多次点击按钮但 没有改变传入的 props(比如 name 不变,count 不变),MyPureComponent不会重新渲染,因为 shouldComponentUpdate返回了 false。
与 React.Component的对比
如果你使用普通的 React.Component,每次父组件更新,即使子组件的 props没有变化,子组件也可能重新渲染,除非你手动实现 shouldComponentUpdate进行优化。
而 React.PureComponent帮你省去了手动写 shouldComponentUpdate的麻烦,对 props和 state进行了浅比较优化。
注意事项
✅ 适合使用 PureComponent的场景:
组件的 props和 state大部分时间是基本类型(如 string, number)或者引用稳定的对象/数组。
你希望减少不必要的渲染,提升性能。
你不想手动写 shouldComponentUpdate。
❌ 不适合使用 PureComponent的场景:
你的 props或 state是嵌套对象或数组,且你经常修改其内部值但保持引用不变(浅比较无法发现变化)。
例如:
// ❌ 不会触发更新,因为 obj 引用没变
this.setState({ obj: this.state.obj });
即使你修改了 obj.someValue,但因为 obj的引用没有变,PureComponent认为 state没变,就不会重新渲染。
你需要深度比较 props 或 state,PureComponent不提供此功能。
替代方案(当 PureComponent 不适用时)
如果你的数据结构复杂,或者你不想用 PureComponent,可以考虑以下方案:
1. 手动实现 shouldComponentUpdate
你可以继承 React.Component并手动比较需要的字段:
shouldComponentUpdate(nextProps, nextState) {// 只有当特定的 prop 改变时才更新if (this.props.value !== nextProps.value) {return true;}return false;
}
2、使用 React.memo(函数组件)
如果是函数组件,推荐使用 React.memo来实现类似的浅比较优化:
const MyMemoComponent = React.memo(function MyComponent(props) {return <div>{props.name}</div>;
});
React.memo对函数组件起到了和 PureComponent对类组件类似的作用。
总结
特性 | React.PureComponent | React.Component | React.memo (函数组件) |
---|---|---|---|
类型 | 类组件基类 | 类组件基类 | 函数组件高阶组件 |
是否自动优化渲染 | ✅ 是(浅比较 props/state) | ❌ 否 | ✅ 是(浅比较 props) |
适用场景 | 类组件,性能敏感,props简单 | 通用类组件 | 函数组件,性能敏感,props简单 |
深度比较支持 | ❌ 否 | ❌ 否 | ❌ 否 |
React 18 支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
React.PureComponent,它是优化类组件渲染性能的有效工具,特别适用于那些 props 和 state 较为简单、引用稳定 的组件。但要注意它只进行浅比较,对于复杂数据结构要谨慎使用,必要时采用其他优化手段。
如果你使用的是函数组件,推荐使用 React.memo来达到类似的优化效果。
十、常用HOOK
1、useMemo
在 React 18 中,useMemo 是一个用于性能优化的钩子函数,它可以缓存计算结果,避免在每次渲染时都执行昂贵的计算。
useMemo 的基本用法
const memoizedValue = useMemo(() => {// 执行昂贵的计算return computeExpensiveValue(a, b);
}, [a, b]); // 依赖数组
关键特点
- 缓存计算结果:只有当依赖数组中的值发生变化时,才会重新执行计算函数
- 依赖数组:与 useEffect 类似,指定哪些值的变化会触发重新计算
- 返回值:返回缓存的计算结果,而不是函数
使用场景
- 避免重复进行昂贵的计算(如复杂的数学运算、大数据处理)
- 优化子组件渲染(当传递给子组件的 props 是对象或数组时)
示例:优化昂贵计算
import React, { useState, useMemo} from "react";function Child() {let [num1, setNum1] = useState(0);let [num2, setNum2] = useState(1);// 使用 useMemo 缓存计算结果const doubleNumber = useMemo(() => {console.log("Calculating double number");return slowFunction(num1);}, [num1]); // 只有 number 变化时才重新计算return (<div><p>num1: {num1} <button onClick={() => setNum1(++num1)}>++num1</button></p><p>num2: {num2} <button onClick={() => setNum2(++num2)}>++num2</button></p><p>doubleNumber: {doubleNumber}</p></div>);
}function slowFunction(num) {console.log('Calling slow function');for (let i = 0; i < 1000000000; i++) {} // 模拟耗时操作return num * 2;
}export default Child;
注意事项
- 不要过度使用:useMemo 本身也有开销,对于简单计算,直接计算可能比缓存更高效
- 引用稳定性:useMemo 可以确保返回值的引用稳定,这在传递给依赖引用相等性的 hooks(如 useEffect)时很有用
- React 18 中的变化:在并发渲染中,useMemo 可能会在某些情况下重新计算,因此不应依赖它来产生副作用
useMemo 是性能优化的工具,应在确认存在性能问题后再使用,而不是过早优化。
2、useCallback()
用于记忆化(缓存)一个函数,避免在每次组件重新渲染时都创建一个新的函数实例,从而优化性能,特别是在将函数作为 props 传递给子组件且子组件使用了 React.memo等优化手段时。
基本语法
const memoizedCallback = useCallback(() => {// 你要执行的函数逻辑},[dependencies] // 依赖项数组
);
- 第一个参数:你要记忆化的函数。
- 第二个参数:依赖项数组,只有当这些依赖发生变化时,才会重新创建这个函数。
为什么要用 useCallback?
在 React 中,每次组件重新渲染时,函数组件内部的函数也会重新创建。如果这个函数被传递给子组件,而且子组件使用了类似 React.memo进行优化,那么每次父组件更新即使子组件的 props 没有实质变化,也可能因为函数引用不同而导致子组件不必要的重新渲染。
使用 useCallback可以确保函数在依赖不变的情况下保持引用不变,从而避免这类问题。
使用示例
父组件
import React, {useState, useCallback} from 'react';
import './App.css';
import Child from "./Child"function App() {let [num, setNum] = useState(0);let [count, setCount] = useState(0);const handleClick = useCallback(() => {console.log('Button clicked!', num);}, [num])console.log("App Render");return (<div className="App"><div>Parent num: {num} <button onClick={() => setNum(++num)}>change num</button></div><div>Parent count: {count} <button onClick={() => setCount(++count)}>change count</button></div><Child onClick={handleClick}/></div>);
}export default App;
子组件
import React, { useMemo, useState } from 'react';function Child(props){console.log("Child Render");return (<div><button onClick={() => props.onClick()}>Click Me</button></div>);
}export default React.memo(Child);
说明:
每次点击父组件中的 “change num” 按钮,num更新,导致父组件重新渲染。
如果没有使用 useCallback,handleClick函数每次都会重新创建,即使传给 Child的其他 props 没变,Child也可能会重新渲染(尽管用了 React.memo)。
使用了 useCallback后,只要 num没变,handleClick函数引用就不变,配合 React.memo,可以避免子组件不必要的渲染。
注意事项
1、不要滥用 useCallback
不是所有函数都需要用 useCallback包裹,只有当函数作为 prop 传递给优化过的子组件(如 React.memo),或者用作其他依赖(如 useEffect, useMemo等)时,才考虑使用它来优化性能。
过度使用 useCallback反而会让代码变得复杂且可能影响性能(比如依赖项处理不当)。
2、依赖项要写全
与 useEffect类似,useCallback的依赖项数组必须包含函数内部用到的所有外部变量,否则可能导致闭包问题,拿到的是旧的变量值。
3、useCallback 不会记忆化函数的返回值
它只是记忆化函数本身(即引用不变),不是用来缓存函数返回值的。如果需要缓存返回值,请使用 useMemo。
总结
场景 | 是否推荐使用 useCallback |
---|---|
函数作为普通 props 传给子组件,子组件未优化 | 一般不需要 |
函数作为 props 传给 | 推荐使用,避免子组件因函数引用变化而 re-render |
函数用作 | 推荐使用,保证引用稳定 |
函数不涉及上述情况,仅内部使用 | 通常不需要 |
useCallback vs useMemo
Hook | 作用 | 适用场景 |
---|---|---|
| 缓存函数,返回同一个函数引用 | 用于传递给子组件或作为其他 Hook 的依赖 |
| 缓存计算结果,返回值不变 | 用于缓存耗时的计算结果 |