注意:hooks只能在函数(无状态组件)中使用
当父组件引入子组件的情况下,往往会造成组件之间的一些不必要的浪费,下面我们通过例子来了解一下场景
import React, { useState } from 'react';
function Test() {
const [count, setCount] = useState<number>(100);
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
<TestChild/>
</>
)
}
export default Test;
//创建一个子组件
function TestChild(): JSX.Element {
console.log('TestChild运行了?');
return(
<h3>我是子组件</h3>
);
}
这时我们开启服务运行一下这个小案例,会发现,我们的子组件并没有对应的需要更新的操作但是还是触发了,这时候我们需要使用React的memo来优化一下代码
import React, { useState,memo } from 'react';
//在TestChild子组件使用之前,使用memo包裹一下
const MemoTestChild = memo(TestChild);//对子组件进行处理
function Test() {
const [count, setCount] = useState<number>(100);
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
{/* */}
<MemoTestChild/>
</>
)
}
export default Test;
//创建一个子组件
function TestChild(): JSX.Element {
console.log('TestChild运行了?');
return(
<h3>我是子组件</h3>
);
}
这时我们看一下会不会产生上述的问题
从上面可以看出,除了初始化的执行之外,这时候父组件发生状态的改变,子组件不会发生对应的重新执行,优化了代码的性能,个人建议多使用这些性能优化的函数,以提高性能
import React, { useState, memo } from 'react';
//memo性能优化后的子组件
const MemoTestChild = memo(TestChild);
function Test(): JSX.Element {
const [count, setCount] = useState<number>(100);
const [name, setName] = useState<string>('TestChild组件');
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
{/* 把父组件的状态和设置状态的函数传递给子组件 */}
<MemoTestChild name={name} onClick={(newName: string) => setName(newName)} />
</>
)
}
export default Test;
//子组件部分
interface TestChildPropsType {
name: string;
onClick: Function;
}
function TestChild({ name,onClick }: TestChildPropsType): JSX.Element {
console.log('TestChild运行了?');
return (
<>
<h3>我是子组件,这是父组件传递过来的数据:{name}</h3>
<button onClick={onClick.bind(null,'新的子组件name')}>改变name</button>
</>
);
}
这是传递给子组件一个新的状态,然后我们看看点击父组件后的情况
这时我们发现,当我们传递状态给子组件的时候,memo好像没什么效果,子组件还是执行了,这时候我们就要引入hooks的useCallback、useMemo这两个钩子了,不知道的大佬可以观看官方文档的介绍,这里就不做介绍了,只是一些实际场景的运用
官方useCallback、useMemo地址
useCallback:把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
useMemo:把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
两个都是返回一个memoized,同时具备第二个参数依赖项,第二个参数的情况和useEffect类似,但是useCallback往往使用于传递给子组件的函数的优化,useMemo使用于数据的优化
import React, { useState, memo, useCallback } from 'react';
//memo性能优化后的子组件
const MemoTestChild = memo(TestChild);
function Test(): JSX.Element {
const [count, setCount] = useState<number>(100);
const [name, setName] = useState<string>('TestChild组件');
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
{/* 把父组件的状态和设置状态的函数传递给子组件 */}
<MemoTestChild name={name} onClick={useCallback((newName: string) => setName(newName),[])} />
{/* useCallback((newName: string) => setName(newName),[]) */}
{/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数 */}
</>
)
}
export default Test;
//子组件部分
interface TestChildPropsType {
name: string;
onClick: Function;
}
function TestChild({ name, onClick }: TestChildPropsType): JSX.Element {
console.log('TestChild运行了?');
return (
<>
<h3>我是子组件,这是父组件传递过来的数据:{name}</h3>
<button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
</>
);
}
这时候我们看见子组件不会在父组件与子组件无关状态改变的时候执行,不会一直产生重新产生新函数,useCallback第二个参数,是依赖项,可以确定在什么状态改变的情况下产生一个新的回调函数
import React, { useState, memo, useCallback } from 'react';
//memo性能优化后的子组件
const MemoTestChild = memo(TestChild);
function Test(): JSX.Element {
const [count, setCount] = useState<number>(100);
const [name, setName] = useState<string>('TestChild组件');
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
{/* 把父组件的状态和设置状态的函数传递给子组件 */}
<MemoTestChild
name={{ name, color: name.indexOf('name') !== -1 ? 'red' : 'green' }}
onClick={useCallback((newName: string) => setName(newName), [])}
/>
{/* useCallback((newName: string) => setName(newName),[]) */}
{/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数 */}
</>
)
}
export default Test;
//子组件部分
interface TestChildPropsType {
name: { name: string; color: string };
onClick: Function;
}
function TestChild({ name, onClick }: TestChildPropsType): JSX.Element {
console.log('TestChild运行了?');
return (
<>
<h3 style={{ color: name.color }}>我是子组件,这是父组件传递过来的数据:{name.name}</h3>
<button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
</>
);
}
这时候我们会发现,子组件还是一样的执行了,在父组件更新其它状态的情况下,子组件的name对象属性会一直发生重新渲染改变,从而导致一直执行,这也是不必要的性能浪费
import React, { useState, memo, useCallback, useMemo } from 'react';
//memo性能优化后的子组件
const MemoTestChild = memo(TestChild);
function Test(): JSX.Element {
const [count, setCount] = useState<number>(100);
const [name, setName] = useState<string>('TestChild组件');
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>++</button>
{/* 引入子组件 */}
{/* 把父组件的状态和设置状态的函数传递给子组件 */}
<MemoTestChild
// 使用useMemo,返回一个和原本一样的对象,第二个参数是依赖性,当name发生改变的时候,才产生一个新的对象
name={useMemo(() => ({ name, color: name.indexOf('name') !== -1 ? 'red' : 'green' }),[name])}
onClick={useCallback((newName: string) => setName(newName), [])}
/>
{/* useCallback((newName: string) => setName(newName),[]) */}
{/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数 */}
</>
)
}
export default Test;
//子组件部分
interface TestChildPropsType {
name: { name: string; color: string };
onClick: Function;
}
function TestChild({ name, onClick }: TestChildPropsType): JSX.Element {
console.log('TestChild运行了?');
return (
<>
<h3 style={{ color: name.color }}>我是子组件,这是父组件传递过来的数据:{name.name}</h3>
<button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
</>
);
}
这样我们的一个性能优化小例子就都完成了
最后总结:在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可。而在使用值和函数的情况,需要考虑有没有函数传递给子组件使用useCallback,值有没有所依赖的依赖项而使用useMemo,而不是盲目使用这些hooks等
React进阶用法和hooks的个人使用见解:
1.lazy+Suspense懒加载的使用
2.hooks的useState、useEffect、自定义钩子的实际使用
4.useReducer+useContext+createContext的使用、模拟redux合并reducer
5.useRef,useImperativeHandle和forwardRef的结合使用以及useLayoutEffect、useDebugValue的简单使用