函数组件
大纲链接 §
[toc]
函数组件比 class
组件简单
1. 函数组件创建方式 ⇧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// Arrow Function Component
const Hello = (props) => {
return (
<div>
{props.message}
</div>
)
}
// 简写
const Hello = props => <div>{props.message}</div>
// Function Component
function Hello(props) {
return (
<div>
{props.message}
</div>
)
}
|
函数组件完全替代 Class 组件需要解决的两个问题 ⇧
- 函数组件没有
state
怎么办
- 函数组件没有 生命周期 怎么办
函数组件没有 state
怎么办
React v16.8+
推出 Hooks API
- 其中的一个
API
叫做 useState
可以解决 state
的问题
- 示例,实现
+1
的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import {useState} from 'react'
cosnt App = props => {
const [n, setN] = useState(0)
const onClick = () => { setN(n + 1) }
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
export default App
|
函数组件没有 生命周期 怎么办
React v16.8+
推出 Hooks API
- 其中的一个
API
叫做 useEffect
可以解决 生命周期 的问题
useEffect
用来解决副作用
- 模拟
compnentDidMount
:
useEffect( () => {console.log('第一次渲染')}, [] )
- 模拟
compnentDidUpdate
:
useEffect( () => {console.log('任意属性变更')})
useEffect( () => {console.log('属性n变了')}, [n, ...] )
- 模拟
compnentWillUnmount
:
useEffect( console.log('第一次渲染后'); return () => {console.log('组件将要卸载')} )
模拟其他生命周期
- 模拟
constructor
:函数组件执行的时候,就相当于constructor
- 模拟
shouldComponentUpdate
:使用React.memo
和useMemo
可以解决
- 模拟
render
:函数组件的返回值就是 render
的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
cosnt App = props => {
console.log('这里相当于执行了 constructor')
const X = () => {
const n = 1
return <div>xxx</div>
}
const Y = () => {
const n = 2
return <div>yyy</div>
}
return (
{/* 这里可以写一些逻辑 */}
<>
<X /> <Y />
这里相当于执行了 render
</>
)
}
export default App
|
2. 详解函数组件模拟生命周期 ⇧
模拟componentDidMount
: ⇧
- 不能直接在函数内访问
n
:console.log(n)
,否则每次渲染都会调用,不能做到只有第一次调用
- 使用
useEffect(() => {}, [])
- 默认只有第一个参数的情况下,效果和不使用
hooks
写在外部一样,每次渲染都执行
- 传第二个参数为
[]
空数组,则可以实现只在第一次时调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import {useState, useEffect} from 'react'
cosnt App = props => {
const [n, setN] = useState(0)
const onClick = () => { setN(n + 1) }
// console.log(n) // 每次渲染都会调用,不能做到只有第一次调用
useEffect(() => {
console.log('调用 useEffect')
}, [])
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
export default App
|
部分模拟componentDidUpdate
: ⇧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import {useState, useEffect} from 'react'
cosnt App = props => {
const [n, setN] = useState(0)
const [m, setM] = useState(0)
const onClickN = () => { setN(n + 1) }
const onClickM = () => { setM(m + 1) }
useEffect(() => {
console.log('n, m 更新了') // 第一次也会执行
}, [n, m])
return (
<div>
{n}
{m}
<button onClick={onClickN}> n+1 </button>
<button onClick={onClickM}> m+1 </button>
</div>
)
}
export default App
|
- 使用
useEffect(() => {}, [n, m, ...])
- 第一次也会执行,而
componentDidUpdate
第一次不会执行
- 将需要追踪变化的属性写在第二个参数的数组中
- 不写第二个参数,则追踪所有属性变化
useEffect( () => {} )
模拟componentWillUnmount
: ⇧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import {useState, useEffect} from 'react'
cosnt App = props => {
const [childVisible, setChildVisible] = useState(true)
const hide = () => { setChildVisible(false) }
const show = () => { setChildVisible(true) }
return (
<div>
{childVisible
? <button onClick={hide}>hide</button>
: <button onClick={show}>show</button>
}
{childVisible
? <Child />
: null
}
</div>
)
}
const Child = props => {
useEffect( () => {
console.log('渲染或变化了')
return () => {
console.log('Child销毁了')
}
})
return (<div>Child</div>)
}
export default App
|
- 使用
useEffect(() => () => {} )
函数组件模拟生命周期钩子函数小结
- 能用函数组件就用函数组件,更简单
React
通过使用 useEffect
不同的参数与返回值组合,来模拟实现类组件中的三个生命周期
3.自定义 Hooks
之 useUpdate
⇧
引入一个计数状态 ⇧
useEffect
会在数据第一次生成时就调用,如何在数据第二次发生变化才调用?
- 通过
useState
引入一个计数状态 [nUpdateCount, setNUpdateCount] = useState(0)
- 当组件挂载后,计数状态
+1
- 通过
useEffect
模拟componentDidMount
已挂载
- 当
nUpdateCount > 1
说明已挂载
- 判断
nUpdateCount > 1
时,才执行后续逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import {useState, useEffect} from 'react'
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(n + 1)
}
// 引入一个计数状态
const [nUpdateCount, setNUpdateCount] = useState(0)
// 挂载后 计数
useEffect(() => {
// setNUpdateCount(nUpdateCount + 1)
setNUpdateCount(x => x + 1)
}, [n])
// 计数状态大于 1 时,才显示 n 变化了
useEffect(() => {
nUpdateCount > 1 && console.log('n变了')
}, [nUpdateCount])
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
// ...
|
- 目前就实现当
n
第一次变化时,才执行 console.log('n变了')
通过将以上逻辑封装为自定义钩子函数组合来实现
封装自定义钩子函数 ⇧
- 这个自定义函数 必须是以
use
开头,例如:useX
- 需要接受初始状态:
const useX = (n) => {/*...*/}
- 返回状态(可选):
return {/*...*/}
- 用解构赋值来接受变量(可选):
const {nUpdateCount} = useX(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import {useState, useEffect} from 'react'
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(n + 1)
}
// 封装为钩子函数
const useX = (n) => {
const [nUpdateCount, setNUpdateCount] = useState(0)
// 挂载后 计数状态 +1 说明已挂载
useEffect(() => {
// setNUpdateCount(nUpdateCount + 1)
setNUpdateCount(x => x + 1)
}, [n])
return {
nUpdateCount
}
}
// 调用 useX() 执行之前相同的逻辑,获取 nUpdateCount
const {nUpdateCount} = useX(n)
// 计数状态 nUpdateCount 大于 1 时,才显示 n 变化了
useEffect(() => {
nUpdateCount > 1 && console.log('n变了')
}, [nUpdateCount])
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
// ...
|
将后续的逻辑放到useX
的第二个参数fn
,为一个回调函数 ⇧
- 在封装的钩子函数中
- 第一个参数接受 状态;第二个参数接受 回调函数
- 判断计数状态是否大于
1
,大于1
,说明已挂载
- 在回调中执行具体的逻辑(即将具体执行的逻辑放在回调中,通过参数传入即可)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
import {useState, useEffect} from 'react'
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(n + 1)
}
// 第一个参数接受 状态;第二个参数接受 回调函数
const useX = (n, fn) => {
// 计数状态
const [nUpdateCount, setNUpdateCount] = useState(0)
// 挂载后 计数状态 +1 说明已挂载
useEffect(() => {
// setNUpdateCount(nUpdateCount + 1)
setNUpdateCount(x => x + 1)
}, [n])
// 判断计数状态是否大于1
// 计数状态 nUpdateCount 大于 1 时,才显示 n 变化了
useEffect(() => {
// nUpdateCount > 1 && console.log('n变了')
nUpdateCount > 1 && fn() // fn() 为传入的第二个参数,执行具体的逻辑
}, [nUpdateCount])
// 当无需暴露计数状态时,可以不写返回值
return {
nUpdateCount
}
}
// 调用 useX() 执行之前相同的逻辑,获取 nUpdateCount
// 并且将逻辑写在第二个参数 回调函数中
// 当无需暴露计数状态时,可以不解构出返回值 直接 useX(n, () => {/*...*/})
const {nUpdateCount} = useX(n, () => {
console.log('n变了') // 将具体执行的逻辑放在回调中,通过参数传入即可
})
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
// ...
|
模仿useEffect
,交换参数位置 useX(fn, n)
⇧
- 也可以将第二个参数改为数组的形式
useX(fn, stateArr)
- 计数状态随便取名
const [nUpdateCount, setNUpdateCount] = useState(0)
- 改为
const [count, setCount] = useState(0)
- 将
useX
改为useUpdate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import {useState, useEffect} from 'react'
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(n + 1)
}
// const useX = (fn, n) => {
// const useX = (fn, stateArr) => { // 交换参数位置
// 更名为 useUpdate
const useUpdate = (fn, stateArr) => {
// 计数状态
// const [nUpdateCount, setNUpdateCount] = useState(0)
const [count, setCount] = useState(0)
// 挂载后 计数状态 +1 说明已挂载
useEffect(() => {
// setNUpdateCount(x => x + 1)
setCount(x => x + 1)
}, stateArr)
useEffect(() => {
// nUpdateCount > 1 && fn()
count > 1 && fn()
}, [count])
}
// useX(() => { // 更名为 useUpdate
useUpdate(() => {
console.log('变了') // 将具体执行的逻辑放在回调中,通过参数传入即可
}, [n])
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
// ...
|
将 useUpdate
放在外部 ⇧
- 第二参数为状态依赖,通过扩展运算符解构为数组,解决警告(可选)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import {useState, useEffect} from 'react'
const useUpdate = (fn, stateArr) => {
// 计数状态
const [count, setCount] = useState(0)
// 挂载后 计数状态 +1 说明已挂载
useEffect(() => {
// setNUpdateCount(x => x + 1)
setCount(x => x + 1)
}, [stateArr])
// }, [...stateArr])
useEffect(() => {
// nUpdateCount > 1 && fn()
count > 1 && fn()
}, [count])
}
const App = () => {
const [n, setN] = useState(0)
const onClick = () => {
setN(n + 1)
}
useUpdate(() => {
console.log('变了') // 将具体执行的逻辑放在回调中,通过参数传入即可
}, n)
return (
<div>
{n}
<button onClick={onClick}> +1 </button>
</div>
)
}
// ...
|
封装为导出一个单独的文件,导入使用 ⇧
useUpdate.js
1
2
3
|
export const useUpdate = (fn, stateArr) => {
/*...*/
}
|
- 其他复用逻辑的地方导入使用
import {useUpdate} from './useUpdate'
useUpdate
小结 ⇧
- 第一次由
undefined
变为 0
时不执行函数
useUpdate
接受一个 回调 和 一个 依赖
- 将传入的依赖放入内部的
useEffect
useEffect
模拟挂载时,依赖改变,将内部计数状态+1
- 再次使用
useEffect
模拟第一次发生改变也执行逻辑
- 此时使用 计数状态 作为 依赖
- 判断内部计数状态大于
1
,说明已挂载,立即执行传入的回调
4. 测试:函数组件 ⇧
参考文章 ⇧
相关文章 ⇧
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名