React组件的挂载和更新顺序
挂载
import React, { useEffect } from "react";
const GrandFather: React.FC = ({ children }) => {
useEffect(() => {
console.log("爷爷挂载了");
}, []);
return (
<div>
我是爷爷
{children}
</div>
);
};
const Father: React.FC = ({ children }) => {
useEffect(() => {
console.log("爸爸挂载了");
}, []);
return (
<div>
我是爸爸
{children}
</div>
);
};
const Me: React.FC = () => {
useEffect(() => {
console.log("孙子挂载了");
}, []);
return <div>我是孙子</div>;
};
const Demo: React.FC = () => {
useEffect(() => {
console.log("老祖宗挂载了");
}, []);
return (
<div>
<GrandFather>
<Father>
<Me />
</Father>
</GrandFather>
</div>
);
};
export default Demo;
结果:
结论:React组件的挂载顺序为:
子孙组件 -> 当前组件 -> 根组件
更新
import React, { useEffect, useState } from "react";
const GrandFather: React.FC<{ random: number }> = ({ children, random }) => {
console.log("我是爷爷");
useEffect(() => {
console.log("%c爷爷更新了", "color: red");
}, [random]);
return (
<div>
我是爷爷, {random}
{children}
</div>
);
};
const Father: React.FC<{ random: number }> = ({ children, random }) => {
console.log("我是爸爸");
useEffect(() => {
console.log("%c爸爸更新了", "color: red");
}, [random]);
return (
<div>
我是爸爸, {random}
{children}
</div>
);
};
const Me: React.FC = () => {
console.log("我是孙子");
console.log("\n");
useEffect(() => {
console.log("孙子更新了");
}, []);
return <div>我是孙子</div>;
};
const Demo: React.FC = () => {
console.log("我是老祖宗");
const [randNum, setRandNum] = useState(Math.random());
useEffect(() => {
console.log("老祖宗挂载了");
}, []);
return (
<div>
<button
type="button"
onClick={() => {
setRandNum(Math.random());
}}
>
点我
</button>
<GrandFather random={randNum}>
<Father random={randNum}>
<Me />
</Father>
</GrandFather>
</div>
);
};
export default Demo;
结果:
结论:组件的更新顺序同组件的挂载顺序,也为
子孙组件 -> 当前组件,组件触发更新,则该组件及以下的所有组件将会触发更新
问题: 没有状态或属性变更的孙子组件也被调用了
React有哪些框架内提供的hook?
useStateuseEffect- useContext
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHadle
- useLayoutEffect
- useDebugValue
useState
什么是?函数组件中使用状态的一种方式何时用?在编写需要状态渲染的函数组件时使用怎么用?第一种:直接传入初始值
const [state, setState] = useState(initialState);
第二种:传入一个初始值初始化的函数(惰性初始化)
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
setState的两种方式:
- 直接传入更新值
- 在使用到依赖旧值得出新值时传入一个函数,返回新值
和class组件有啥不同?
情景一:class组件与函数组件更新状态时传入相同的值
import React from "react";
export default class Demo extends React.Component<any, { random: number }> {
constructor(props: any) {
super(props);
this.state = {
random: Math.random(),
};
}
changeState = () => {
this.setState({
random: 1,
});
};
render() {
console.log("%c rendered...", "color: red");
const { random } = this.state;
return (
<div>
<button type="button" onClick={this.changeState}>
点我
</button>
<p>{random}</p>
</div>
);
}
}
结果>>:
import React from "react";
export default () => {
console.log("%c rendered...", "color: red");
const [num, setNum] = React.useState(Math.random());
return (
<div>
<button type="button" onClick={() => setNum(1)}>
点我
</button>
<div>{num}</div>
</div>
);
};
结果>>:
为什么一摸一样的组件,会产生这样的差异?
原因:
结论:class组件的this.setState每次都会触发更新,即使是相同的state。而hooks的setState会经过比较算法判断,相同的state不会触发更新
知道这个差异对我们的工作有什么指导意义?
import React from "react";
export default () => {
console.log("%c rendered...", "color: red");
const [num, setNum] = React.useState({ a: Math.random() });
return (
<div>
<button
type="button"
onClick={() => {
num.a = Math.random();
setNum(num);
}}
>
点我
</button>
<div>{num.a}</div>
</div>
);
};
import React from "react";
export default class Demo extends React.Component<any, { num: { a: number } }> {
constructor(props: any) {
super(props);
this.state = {
num: {
a: Math.random(),
},
};
}
changeState = () => {
const { num } = this.state;
num.a = Math.random();
this.setState({
num,
});
};
render() {
console.log("%c rendered...", "color: red");
const { num } = this.state;
return (
<div>
<button type="button" onClick={this.changeState}>
点我
</button>
<p>{num.a}</p>
</div>
);
}
}
情景二:更新状态时传入状态对象的部分属性
import React from "react";
interface IState {
a: number;
b: number;
c: number;
}
export default class Demo extends React.Component<any, IState> {
constructor(props: any) {
super(props);
this.state = {
a: 1,
b: 2,
c: 3,
};
}
render() {
console.log("rendered...");
return (
<div>
<button
type="button"
onClick={() => {
this.setState({ a: 4 });
this.setState({ b: 5 });
this.setState({ c: 6 });
}}
>
点我
</button>
<p>state keys: </p>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</div>
);
}
}
import React, { useState } from "react";
interface IState {
a: number;
b: number;
c: number;
}
export default () => {
console.log("rendered...");
const [state, setState] = useState<Partial<IState>>({
a: 1,
b: 2,
c: 3,
});
return (
<div>
<button
type="button"
onClick={() => {
setState({ a: 4 });
setState({ b: 5 });
setState({ c: 6 });
}}
>
点我
</button>
<p>state keys: </p>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
};
结论:class组件setState会自动合并更新对象,而hook setState不会,每次传入的都作为一个新的state
知道这个差异对我们的工作有什么指导意义?
直接影响到编码逻辑的实现
useEffect
import React, { useEffect, useState } from "react";
export default () => {
const [num, setNum] = useState(Math.random());
useEffect(() => {
console.log("加载或更新了");
return () => {
console.log("清除副作用");
};
});
return (
<div>
<button type="button" onClick={() => setNum(Math.random())}>
点我
</button>
<p>{num}</p>
</div>
);
};
effect返回的副作用清除函数,在没有设置依赖或依赖不为空且发生变化时时,每次更新后下一次effect执行前会被执行;在设置依赖为空时,副作用清除函数会在组件被卸载时被执行。
useContext
使用频率相对较低,分享一个使用context默认值和属性值覆盖的写法
import React from "react";
const CustomContext = React.createContext('defaultContextValue');
const Demo: React.FC<{customValue: any}> = ({children, customValue}) => {
return (
<CustomContext.Consumer>
{(value) => <CustomContext.Provider value={customValue || value}>{children}</CustomContext.Provider>}
</CustomContext.Consumer>
);
}
export default Demo;
useReducer
需要熟悉redux, 除useState外,另一种使用state的方式
import React, { useReducer } from "react";
export default () => {
const [count, dispatch] = useReducer((x) => x + 1, 0);
return (
<div>
<button type="button" onClick={dispatch}>
点我
</button>
<p>{count}</p>
</div>
);
};
useCallback
函数组件中函数属性的两种写法:
- 写法一
import React, { useReducer } from "react";
const Child: React.FC<{ onClick: () => void }> = ({ onClick }) => {
console.log("rendered...");
return (
<button type="button" onClick={onClick}>
点我
</button>
);
};
export default () => {
const [num, dispatch] = useReducer((x) => x + 1, 0);
const btnHandle = () => {
console.log(111);
};
return (
<div>
Parent, {num}
<button type="button" onClick={dispatch}>
点我呀
</button>
<Child
onClick={btnHandle}
/>
</div>
);
};
- 写法二
import React, { useReducer } from "react";
const Child: React.FC<{ onClick: () => void }> = React.memo(({ onClick }) => {
console.log("rendered...");
return (
<button type="button" onClick={onClick}>
点我
</button>
);
});
export default () => {
const [num, dispatch] = useReducer((x) => x + 1, 0);
return (
<div>
Parent, {num}
<button type="button" onClick={dispatch}>
点我呀
</button>
<Child
onClick={() => {
console.log(num);
}}
/>
</div>
);
};
两种写法无本质区别,都会在组件更新时重新生成一个新的函数
在某些场景下会引起不必要的渲染,useCallback就是用来生成一个不变的函数
import React, { useReducer, useCallback } from "react";
const Child: React.FC<{ onClick: () => void }> = React.memo(({ onClick }) => {
console.log("rendered...");
return (
<button type="button" onClick={onClick}>
点我
</button>
);
});
export default () => {
const [num, dispatch] = useReducer((x) => x + 1, 0);
return (
<div>
Parent, {num}
<button type="button" onClick={dispatch}>
点我呀
</button>
<Child
onClick={useCallback(() => {
console.log(num);
}, [])}
/>
</div>
);
};
什么是useCallback的依赖?
useCallback的依赖是指在函数中用到的组件的state
那是不是应该把所有函数属性都用useCallback进行包裹呢?
答案显示是否定的,我们可以参考不要过度使用useCallback()一文的分析
useMemo
import React, { useState } from "react";
const User: React.FC<{ info: { name: string; age: number } }> = React.memo(
({ info }) => {
console.log("rendered...");
return (
<div>
{info.name}的年纪是{info.age}
</div>
);
}
);
const Demo: React.FC = () => {
const [count] = useState(0);
const [value, setValue] = useState(0);
const userInfo = {
name: "Jack",
age: count,
};
return (
<div>
<div>
<User info={userInfo} />
</div>
<div>
{value}
<button
type="button"
onClick={() => {
setValue(value + 1);
}}
>
点我
</button>
</div>
</div>
);
};
export default Demo;
子组件使用了memo,没有依赖value,只是依赖了count,但是结果每次父组件修改了value的值后,虽然子组件没有依赖value,而且使用了memo包裹,还是每次都重新渲染了
使用useMemo后:
const userInfo = React.useMemo(
() => ({
name: "Jack",
age: count,
}),
[count]
);
useMemo用于在渲染期间高开销的计算,减少不必要的组件调和,从而实现性能优化的目的
useRef
useRef是非常有用的hook,除了可以将其返回值传递给ref属性获取一个class组件实例或者一个DOM元素外,由于其current属性可以保存具有记忆能力的任意值,我们可以利用到实现很多功能。
比如,获取一个变量的新旧值:
import React, { useState, useEffect, useRef } from "react";
export default () => {
const [count, setCount] = useState(0);
const prevCountRef = useRef(0);
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<button type="button" onClick={() => setCount((c) => c + 1)}>
点我
</button>
<h1>
Now: {count}, before: {prevCount}
</h1>
</div>
);
};
useImperativeHandle
import React, {
forwardRef,
useImperativeHandle,
useRef,
useEffect,
} from "react";
const Input = (props: any, ref: any) => {
const inputRef: React.LegacyRef<HTMLInputElement> = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
},
}));
return <input type="text" ref={inputRef} />;
};
const FancyInput = forwardRef<HTMLInputElement, {}>(Input);
export default () => {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
// @ts-ignore
inputRef.current.focus();
}
}, []);
return <FancyInput ref={inputRef} />;
};
使用useImperativeHandle,我们可以定义一些api给父组件调用,这些api相当于函数组件对外的接口
自定义hook
什么是?
自定义hook就是使用了react hooks的函数
何时用?
当我们在项目开发过程中使用了react hooks,并在多处重复的逻辑即可抽取为一个自定义hook
例子:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}