【笔记】React的函数式组件

前言

React的函数式(Functional Component)组件学习笔记

定义组件

  • 组件名首字母大写
  • return可以返回的数据类型,与类组件相同
1
2
3
4
5
6
7
const Component = function (props) {
return (
<></>
);
}

export default Component;
  • 通过className代替class属性
1
2
3
4
5
6
7
const Component = function (props) {
return (
<>
<div className=""></div>
</>
);
}
  • 通过htmlFor代替for属性
1
2
3
4
5
6
7
8
9
const Component = function (props) {
return (
<>
<label htmlFor="input">
<input id="input" type="text" />
</label>
</>
);
}

定义纯组件

1
2
3
4
5
6
7
8
9
import { memo } from "react";

const Component = memo(function (props) {
return (
<></>
);
});

export default Component;

父子组件跨多级传递数据

祖先组件传递数据给子孙组件

创建上下文对象

  • 上下文对象名首字母大写
src/AppContext.jsx
1
2
3
import React from "react";

const AppContext = React.createContext();
定义默认值
src/AppContext.jsx
1
2
3
import React from "react";

const AppContext = React.createContext({key: "default"});

组件组件向子孙组件传递数据

  • 使用上下文对象的Provider组件包裹子组件
  • 在上下文对象的Provider组件定义value属性,通过value属性向子孙传递数据
src/GrandFather.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import AppContext from "./AppContext";
import Father from "./Father";

function GrandFather() {
return (
<>
<AppContext.Provider value={ {key: "value"} }>
<Father />
</AppContext.Provider>
</>
);
}

export default GrandFather;
src/Father.jsx
1
2
3
4
5
6
7
8
9
10
11
import Son from "./Son";

function Father() {
return (
<>
<Son />
</>
);
}

export default Father;

子孙组件获取祖先组件传递的数据

通过向Customer中传递回调函数获取数据
src/Son.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import AppContext from "./AppContext";

function Son() {
return (
<>
<AppContext.Consumer>
{
(context) => {
return context.key;
}
}
</AppContext.Consumer>
</>
);
}

export default Son;
  • 这种方式可以获取多种上下文的数据
src/Son.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import AppContext from "./AppContext";

function Son() {
return (
<>
<App1Context.Consumer>
{
(context) => {
return context.key;
}
}
</App1Context.Consumer>
<App2Context.Consumer>
{
(context) => {
return context.key;
}
}
</App2Context.Consumer>
</>
);
}

export default Son;

Hooks

  • 只能在函数的顶层调用Hook函数

组件的状态

定义组件的状态

  • 如果没有定义状态默认值,则默认为undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState } from "react";

const Component = function (props) {
const [ key, setKey ] = useState();

return (
<>
<div>{ key }</div>
</>
);
}

export default Component;
  • 定义默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState } from "react";

const Component = function (props) {
const [ key, setKey ] = useState("default");

return (
<>
<div>{ key }</div>
</>
);
}

export default Component;
  • 多行代码可以传递函数作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState } from "react";

const Component = function (props) {
const [ key, setKey ] = useState(function () {
return "default";
});

return (
<>
<div>{ key }</div>
</>
);
}

export default Component;

修改组件的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useState } from "react";

const Component = function (props) {
const [ key, setKey ] = useState();

function fn() {
setKey("value");
}

return (
<>
<div onClick={ fn }>{ key }</div>
</>
);
}

export default Component;

组件的副作用

  • useEffect()中传递的函数会在组件每次渲染完成之后(首次渲染之后重新渲染之后)立即执行一次
    • useEffect()中传递的函数的返回值是一个函数,这个返回的函数会在组件重新渲染之前卸载之后立即执行一次
    • useEffect()可以在函数式组件中定义多个,会按照定义时的顺序依次执行
  • useEffect()中传递的数组表示回调函数在哪些依赖项改变时才会重新执行
    • 如果数组为空,则表示回调函数只会在首次渲染执行一次
    • 如果没有传递数组,则表示回调函数会在组件每次渲染完成之后都重新执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useEffect } from "react";

const Component = function (props) {
useEffect(function () {

...

return function () {
...
}
}, []);

return (
<></>
);
}

export default Component;

组件的布局副作用

  • useLayoutEffect()useEffect()参数相同,但useEffect()是页面渲染之后异步执行useEffect()传入的函数,而useLayoutEffect()是页面渲染之前同步执行useLayoutEffect()传入的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useLayoutEffect } from "react";

const Component = function (props) {
useLayoutEffect(function () {

...

return function () {
...
}
}, []);

return (
<></>
);
}

export default Component;

上下文对象

创建上下文对象

  • 上下文对象名首字母大写
src/AppContext.jsx
1
2
3
4
5
import React from "react";

const AppContext = React.createContext();

export default AppContext;
  • 定义默认值
src/AppContext.jsx
1
2
3
4
5
import React from "react";

const AppContext = React.createContext({key: "default"});

export default AppContext;

祖先组件向子孙组件传递数据

  • 使用上下文对象的Provider组件包裹子组件
  • 在上下文对象的Provider组件定义value属性,通过value属性向子孙传递数据
src/GrandFather.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import AppContext from "./AppContext";
import Father from "./Father";

class GrandFather extends React.Component {
render() {
return (
<>
<AppContext.Provider value={ {key: "value"} }>
<Father />
</AppContext.Provider>
</>
);
}
}

export default GrandFather;
src/Father.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import Son from "./Son";

class Father extends React.Component {
render() {
return (
<>
<Son />
</>
);
}
}

export default Father;

子孙组件获取祖先组件传递的数据

  • 每当上下文对象中的数据发生改变,函数式组件都会立即重新渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useContext } from "react";
import AppContext from "./AppContext";

const Component = function (props) {
const appContext = useContext(AppContext);

return (
<>
<div>{ appContext.key }</div>
</>
);
}

export default Component;

优化函数

  • useEffect()中传递一个函数,这个函数useEffect()处理后返回一个memoized(记忆的)函数作为useEffect()的结果,这个结果就是被优化后的函数,这个被优化后的函数在依赖不变的情况下,多次定义的时候,返回的是相同的函数(引用)

  • useEffect()中传递的数组表示在哪些依赖项改变时才会返回新的函数

    • 如果数组为空,则表示只会返回相同的函数
    • 如果没有传递数组,则表示只会返回新的函数
  • 在将函数传递给子组件时,使用被useCallback()优化后的函数传递给子组件,可以避免子组件被意外的重新渲染,实现性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useCallback } from "react";

const Component = function (props) {
const fn = function () {
...
}
const memoizedFn = useCallback(fn, []);

return (
<></>
);
}

export default Component;

优化对象

  • useMemo()中传递一个有返回对象的函数,这个返回的对象useMemo()处理后最终返回一个memoized(记忆的)对象作为useMemo()的结果,这个结果就是被优化后的对象,这个被优化后的对象在依赖不变的情况下,多次定义的时候,返回的是相同的对象(引用)

  • useMemo()中传递的数组表示在哪些依赖项改变时才会返回新的对象

    • 如果数组为空,则表示只会返回相同的对象
    • 如果没有传递数组,则表示只会返回新的对象
  • 在将对象传递给子组件时,使用被useMemo()优化后的对象传递给子组件,可以避免子组件被意外的重新渲染,实现性能优化

1
2
3
4
5
6
7
8
9
10
11
12
import { useMemo } from "react";

const Component = function (props) {
const obj = {}
const memoizedObj = useMemo(() => obj, []);

return (
<></>
);
}

export default Component;

不变的引用对象

通过Ref获取DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useRef } from "react";

function Component() {
const divRef = useRef();

function fn() {
const dom = divRef.current;
}

return (
<>
<div ref={ divRef }></div>
</>
);
}

export default Component;
通过Ref获取子组件的DOM
src/components/Son.jsx
1
2
3
4
5
6
7
8
9
10
11
import { forwardRef } from "react";

const Son = forwardRef(function (props, ref) {
return (
<>
<div ref={ ref }></div>
</>
);
});

export default Son;
src/components/Father.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useRef } from "react";
import Son from "./Son";

const Father = function () {
const divRef = useRef();

function fn() {
const dom = divRef.current;
}

return (
<>
<Son ref={ divRef } />
</>
);
}

export default Father;
在子组件中重写父组件的引用
  • 子组件只暴露特定功能给父组件作为引用
src/components/Son.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { forwardRef, useRef, useImperativeHandler } from "react";

const Son = forwardRef(function (props, ref) {
const divRef = useRef();

useImperativeHandler(ref, () => ({
fn: () => {
const dom = divRef.current;
}
}));

return (
<>
<div ref={ divRef }></div>
</>
);
});

export default Son;
  • 父组件通过获取子组件的引用只能调用子组件暴露的特定功能
src/components/Father.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useRef } from "react";
import Son from "./Son";

const Father = function () {
const divRef = useRef();

function fn() {
divRef.current.fn();
}

return (
<>
<Son ref={ divRef } />
</>
);
}

export default Father;

通过Ref定义一个不会改变的对象

  • 通过Ref定义一个不会改变的对象,并绑定一个值,避免闭包陷阱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useCallback, useRef } from "react";

function Component() {
const [key, setKey] = useState(0);
const keyRef = useRef();
function fn() {
setKey(keyRef.current + 1);
}
const memoizedFn = useCallback(fn, []);

return (
<></>
);
}

export default Component;

在同构应用中生成相同编号

  • 在同构应用中生成不论是服务端渲染还是客户端渲染都相同的编号
1
2
3
4
5
6
7
8
9
10
11
import { useId } from "react";

function Component() {
const id = useId();

return (
<></>
);
}

export default Component;

降低函数执行的优先级

  • startTransition()中传入的函数优先级低于页面渲染
  • startTransition()中传入的函数没有执行完成时,isPending结果为true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useTransition } from "react";

function Component() {
const [isPending, startTransition] = useTransition();
function fn() {
...
}
startTransition(fn);

return (
<>
{ isPending && <div>Loading...</div> }
</>
);
}

export default Component;

暂缓更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useDeferredValue, useState } from "react";

function Component() {
const [key, setKey] = useState();
const deferredKey = useDeferredValue(key);

return (
<>
{ deferredKey }
</>
);
}

export default Component;

自定义Hook

  • 严格意义上自定义Hook只是代码逻辑的抽取,不是React的特性

  • 自定义Hook是以use为函数名前缀的函数

1
2
3
function useFn() {
...
}

完成