函数组件
大纲链接 §
[toc]
函数组件比
class组件简单
1. 函数组件创建方式 ⇧
|
|
函数组件完全替代 Class 组件需要解决的两个问题 ⇧
- 函数组件没有
state怎么办 - 函数组件没有 生命周期 怎么办
函数组件没有
state怎么办
React v16.8+推出Hooks API- 其中的一个
API叫做useState可以解决state的问题
- 其中的一个
示例,实现
+1的功能1 2 3 4 5 6 7 8 9 10 11 12 13import {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消除了
this,大大降低了心智负担,简化代码量
函数组件没有 生命周期 怎么办
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 21cosnt 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 19import {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: ⇧
|
|
- 使用
useEffect(() => {}, [n, m, ...])- 第一次也会执行,而
componentDidUpdate第一次不会执行 - 将需要追踪变化的属性写在第二个参数的数组中
- 不写第二个参数,则追踪所有属性变化
useEffect( () => {} )
- 第一次也会执行,而
模拟componentWillUnmount: ⇧
|
|
- 使用
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 28import {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 39import {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 46import {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改为useUpdate1 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 40import {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 36import {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
|
|
- 其他复用逻辑的地方导入使用
import {useUpdate} from './useUpdate'
useUpdate小结 ⇧
- 第一次由
undefined变为0时不执行函数- 第二次变化时,执行函数
- 第n次变化时,执行函数
useUpdate接受一个 回调 和 一个 依赖- 将传入的依赖放入内部的
useEffect useEffect模拟挂载时,依赖改变,将内部计数状态+1- 再次使用
useEffect模拟第一次发生改变也执行逻辑- 此时使用 计数状态 作为 依赖
- 判断内部计数状态大于
1,说明已挂载,立即执行传入的回调
4. 测试:函数组件 ⇧
·未完待续·
参考文章 ⇧
相关文章 ⇧
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名