学习react学习了已经很久了,在工作中的实践过程中也是实践了两年多。虽然很多时候能够正常的完成相关需求,但是写出代码和写出好的代码还是存在很大的区别。一个好的程序员应该对自己的代码有所追求,而不仅仅是实现功能,更要优雅的实现功能,方便后面的人维护。
之前写组件的时候很多时候是用类组件,以下是一些常用的生命周期函数。
生命周期
组件的生命周期 一般分为:挂载阶段,更新阶段,卸载阶段
组件实例被创建,并插入dom时,生命周期调用如下:
constructor()
getDerivedStateFromProps() // 返回一个对象来更新state,如果返回null则不更新任何内容
render()
componentDIdMount() // 依赖于dom实例化,通常会在这里进行接口的请求
更新阶段
getDerivedStateFromProps() // 返回一个对象来更新state,如果返回null则不更新任何内容
shouldComponentUpdate() // 性能优化的生命周期函数,根据返回值 表示是否需要刷新
render()
getSnapshotBeforeUpdate() // 返回值作为入参传给componentDIdUpdate
componentDIdUpdate() // 在更新后立即调用,可在此处进行网络请求
卸载阶段
componentWillUnmount // 在组件卸载之前调用,多用于清理操作,如清除定时器等
以上就是类组件的所有生命周期函数,但是以下生命周期函数可能在开发过程中造成Bug,应减少使用,(componentWillMount()
,componentWillUpdate()
,componentWillReceiveProps()
),这类生命周期函数由于react的框架改动,对于更新改为时间片的更新,需要区分事件优先级,这些生命周期函数可能会因为时间片的暂停和执行 而进行多次的执行,造成难以察觉且严重的bug。
后面自己再写的时候,就变得用函数组件比较多了。函数组件更加轻巧,而且易于封装,便于拆分。更适用于组件化的思想。下面记录以下react hooks中的一些注意点。
useRef
ref可用于可记住组件的某些信息,但是又不想触发新的渲染。例如可以用于输入框的聚焦效果。
useRef返回一个一个{current: x} // x为你向useRef传的值。
大多时候我们都会用state来存取变量,一下是state和ref的区别。
ref | state |
---|---|
useRef(initialValue) 返回 { current: initialValue } | useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue] ) |
更改时不会触发重新渲染 | 更改时触发重新渲染。 |
可变 —— 你可以在渲染过程之外修改和更新 current 的值。 | “不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 |
你不应在渲染期间读取(或写入) current 值。 | 你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照。 |
什么时候需要用到useRef?
- 存储timeout id
2.存储和操作dom
3.存储不需要被用来计算 JSX 的其他对象
默认情况下,组件不暴露其 DOM 节点。 您可以通过使用 forwardRef
并将第二个 ref
参数传递给特定节点来暴露 DOM 节点。
如果你尝试将 ref 放在 你自己的 组件上,例如 <MyInput />
,默认情况下你会得到 null
。下面是 MyInput
如何使用 forwardRef
API:
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
useEffect
这边记录一下使用useEffect自己踩得一些坑。
实现功能是播放器的功能,播放器的话,播放和暂停是触发play() 和 pause() 方法,然后还有一个进度条的展示,然后我初步展示的时候,是用一个变量来定义进度条百分比。这样结构就有一定的弊端,一个内容,却用两个变量进行维护,十分的冗余。
所以可以用一个ref保存变量,通过useEfftct来判断是否播放来调用相关方法。
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});
return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
const [isPlaying, setIsPlaying] = useState(false);
return (
<>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? '暂停' : '播放'}
</button>
<VideoPlayer
isPlaying={isPlaying}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
/>
</>
);
}
依赖数组中可以省略ref
React 保证 每轮渲染中调用 useRef
所产生的引用对象时,获取到的对象引用总是相同的,即获取到的对象引用永远不会改变,所以它不会导致重新运行 Effect。
useEffect的一些注意事项
- 与事件不同,Effect 是由渲染本身,而非特定交互引起的。
- Effect 允许你将组件与某些外部系统(第三方 API、网络等)同步。
- 默认情况下,Effect 在每次渲染(包括初始渲染)后运行。
- 如果 React 的所有依赖项都与上次渲染时的值相同,则将跳过本次 Effect。
- 不能随意选择依赖项,它们是由 Effect 内部的代码决定的。
- 空的依赖数组(
[]
)对应于组件“挂载”,即添加到屏幕上。 - 仅在严格模式下的开发环境中,React 会挂载两次组件,以对 Effect 进行压力测试。
- 如果 Effect 因为重新挂载而中断,那么需要实现一个清理函数。
- React 将在下次 Effect 运行之前以及卸载期间这两个时候调用清理函数。
- 如果你可以在渲染期间计算某些内容,则不需要使用 Effect。
- 想要缓存昂贵的计算,请使用
useMemo
而不是useEffect
。 - 想要重置整个组件树的 state,请传入不同的
key
。 - 想要在 prop 变化时重置某些特定的 state,请在渲染期间处理。
- 组件 显示 时就需要执行的代码应该放在 Effect 中,否则应该放在事件处理函数中。
- 如果你需要更新多个组件的 state,最好在单个事件处理函数中处理。
- 当你尝试在不同组件中同步 state 变量时,请考虑状态提升。
- 你可以使用 Effect 获取数据,但你需要实现清除逻辑以避免竞态条件。
对于一些高昂的计算,应该使用useMemo而不是useEffect.
const result = useMemo(() => {
// 这里是一些复杂的计算
return XX
}, [])