场景一 : hook的挂载和卸载顺序
常用的两种情况
情况一 :
// 父组件
const App = () => {
const [count,setCount]= useState<number>(0)
useEffect(() => {
console.log(" ===== root")
return () => {
console.log(" ===== unroot")
}
})
return (
<div>
<p>{count}</p>
<Button onClick={() => {
setCount(() => {
return count + 1
})
}}>
+
</Button>
<Child count={count}></Child>
</div>)
}
// 子组件
const Child: React.FC<IProps> = (props) => {
useEffect(() => {
console.log(" ===== child1")
return () => {
console.log(" ====== unchild1")
}
})
return (
<div>
Child: {props.count}
<ChildButton></ChildButton>
</div>
)
}
// 子组件 Button
const ChildButton = () => {
useEffect(() => {
console.log(" ====== child1-button");
return () => {
console.log(" ====== un child1-button");
}
})
return <div>Button</div>
}
export default App
挂载
卸载
情况二 :
const App = () => {
const [count,setCount]= useState<number>(0)
useEffect(() => {
console.log(" ====== root")
return () => {
console.log(" ====== unroot")
}
})
return (
<div>
<p>{count}</p>
<Button onClick={() => {
setCount(() => {
return count + 1
})
}}>
+
</Button>
{count>=2 && count<=3 ? <Child2 count={count}></Child2>: <span>占位</span>}
</div>)
}
// 子组件
const Child2: React.FC<IProps> = (props) => {
useEffect(() => {
console.log(" ===== child2")
return () => {
console.log(" ===== unchild2")
}
})
return (
<div>
Child: {props.count}
<Child2Button></Child2Button>
</div>
)
}
// 子组件 Button
const Child2Button = () => {
useEffect(() => {
console.log(" ===== child2-button");
return () => {
console.log(" ===== un child2-button");
}
})
return <div>Button</div>
}
export default App
挂载
卸载
**总结: **
1 - 组件挂载时先执行子组件的hook, 再执行父组件的hook
2 - 组件卸载时分两种情况:
- 仅发生渲染时,先执行子组件的hook,再执行父组件的hook
- 直接卸载时,先执行父组件的hook,再执行子组件的hook
3 - 组件会先标记要卸载的组件,先执行要卸载的组件 hook(unmount),再执行仅渲染的组件 hook (unmount)
场景二 : useMemo useCallback优化
普通场景优化 :
const Child: React.FC<IProps> = ({count}) => {
const value = useMemo(() => {
console.log("====== compute", count);
return count + 1
},[count])
const clickMe = useCallback(() => {
console.log("====== click me", count);
},[count])
return (
<div>
计算的值: {value}
<Button onClick={clickMe}></Button>
</div>
)
}
useCallback二次优化 :
const Child: React.FC<IProps> = ({count}) => {
const value = useMemo(() => {
console.log("====== compute", count);
return count + 1
},[count])
const clickMe = useCallback((data:number) => {
console.log("====== click me", data);
},[])
return (
<div>
计算的值: {value}
<Button onClick={clickMe.bind(this,count)}></Button>
</div>
)
}
**总结: **
1 - useMemo: 只有在依赖项改变的时候重新计算值,避免组件每次渲染的时候重新计算
2 - useCallback: 只有在依赖项改变的时候返回最新的函数,节省内存空间,优化性能
场景三 : hooks 依赖项数组中每项是通过 Object.is 比较的
const App = () => {
const [data, setData] = useState<{ [key: string]: number }[]>([]);
const newData = useMemo(() => {
console.log("======= memo", data);
return JSON.stringify(data);
}, [data])
// 期望:数组中值发生改变时,才重新计算
useEffect(() => {
const keys = Object.keys(newData);
console.log("========== root", keys);
}, [newData])
console.log("====== rerender...");
return (
<div>
<p>{JSON.stringify(data, null, 2)}</p>
<Button
onClick={() => {
console.log("===== click1", data);
// 1. 引用没改变,值改变,组件没有发生重新渲染
setData(() => {
data.push({ a: 1 });
return data
})
}}
>
+
</Button>
<Button
onClick={() => {
console.log("===== click2", data);
// 2. 引用改变,值没改变,做了无用的渲染
setData([...data])
}}
>
-
</Button>
</div>
);
}
**总结: **
1 - useState 前后两次的值是通过Object.is 去比较的
2 - useMemo useCallback 依赖项中的值对比策略也是 Object.is
场景四 : 有多个state 需要调用多个setState 可以用useReducer优化
const App = () => {
const [title,setTitle] = useState('')
const [text,setText] = useState('')
const [open,setOpen] = useState(false)
const handleChange = (value:number) => {
switch (value) {
case 11:
setTitle('新闻联播')
setText("《新闻联播》是中国中央电视台每日晚间播出的一档新闻节目,被称为“中国政坛的风向标”,节目宗旨为“宣传党和政府的声音,传播天下大事")
setOpen(true)
break;
case 4:
setTitle('共同关注')
setText("《共同关注》是CCTV-13新闻频道一档以公益慈善为品牌特色的日播专题栏目")
setOpen(true)
break;
default:
setTitle('')
setText('')
setOpen(false)
break;
}
}
return (
<div>
<Radio.Group onChange={(e) => handleChange(e.target.value)} defaultValue={0}>
<Radio value={0}>关闭</Radio>
<Radio value={11}>CCTV11</Radio>
<Radio value={4}>CCTV4</Radio>
</Radio.Group>
<h2>电源:{open?'开':'关'}</h2>
<h3>{title}</h3>
<h4>{text}</h4>
</div>
)
}
export default App
优化后
import { useReducer } from "react"
import {Radio} from 'antd'
const App = () => {
interface IData{
title:string,
text:string,
open:boolean
}
type Action = {
type:0
} | {
type:11
} | {
type:4
}
const reducer = (state:IData,action:Action) => {
switch (action.type) {
case 11:
return {
title:'新闻联播',
text:'"《新闻联播》是中国中央电视台每日晚间播出的一档新闻节目,被称为“中国政坛的风向标”,节目宗旨为“宣传党和政府的声音,传播天下大事"',
open:true
}
case 4:
return {
title:'共同关注',
text:"《共同关注》是CCTV-13新闻频道一档以公益慈善为品牌特色的日播专题栏目",
open:true
}
default:
return {
title:'',
text:'',
open:false
}
}
}
const [data,dispatch] = useReducer(reducer,{
title:'',
text:'',
open:false
})
return (
<div>
<Radio.Group onChange={(e) => dispatch({type:e.target.value})} defaultValue={0}>
<Radio value={0}>关闭</Radio>
<Radio value={11}>CCTV11</Radio>
<Radio value={4}>CCTV4</Radio>
</Radio.Group>
<h2>电源:{data.open?'开':'关'}</h2>
<h3>{data.title}</h3>
<h4>{data.text}</h4>
</div>
)
}
export default App
**总结: **
1 - useReducer 可以合并多个 useState 有两点好处:
1. 使代码更优雅,好比如设计模式中的策略模式
2. 减少组件多次的重复渲染