实例讲解:React Hook的思考
翻译原文:https://wattenberger.com/blog/react-hooks
React在一年前引入了hooks,对于许多开发人员来说,它们已经改变了我们的编码习惯,但我想谈一谈从React类组件切换到功能组件基本变化。
使用类组件,我们将更新放置在特定的生命周期事件中
❝类组成中
❞
class Chart extends Component {
componentDidMount() {
//挂载时
}
componentDidUpdate(prevProps) {
if (prevProps.data == props.data) return
// 数据更新时
}
componentWillUnmount() {
// 卸载时
}
render() {
return (
<svg className="Chart" />
)
}
}
在功能组件中,我们改为使用useEffect挂钩在主要生命周期事件期间运行代码。
❝功能组件中
❞
const Chart = ({ data }) => {
useEffect(() => {
// 挂载时
// 数据更新时
return () => {
}
}, [data])
return (
<svg className="Chart" />
)
}
此时,您可能在想:所以,useEffect() 这只是一种进入生命周期事件的新方法!但是这种理解是错误的!
嗯,让我们看一个具体的例子,看看有什么区别。例如,如果我们有一个计算量大的 getDataWithinRange()函数,该函数基于指定的函数返回经过过滤的数据集,该dateRange怎么办?因为getDataWithinRange()运行需要一些时间,所以我们希望将其存储在组件的state对象中,并且仅在dateRange发生更改时进行更新。
类组成:
对于生命周期事件,我们需要一站式处理所有更改。我们的想法如下所示:
当我们的组件加载时,以及props更改时(其实就是dateRange),就相对的更新data
大致如下:
class Chart extends Component {
state = {
data: null,
}
componentDidMount() {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
componentDidUpdate(prevProps) {
if (prevProps.dateRange != this.props.dateRange) {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
}
render() {
return (
<svg className="Chart" />
)
}
}
功能组成
在功能组件中,我们需要考虑哪些值保持同步。每次更新的流程更像以下语句:
保持dateRange同步data
大致如下:
const Chart = ({ dateRange }) => {
const [data, setData] = useState()
useEffect(() => {
//假设getDataWithinRange在外部定义了,暂时不考虑这个
const newData = getDataWithinRange(dateRange)
setData(newData)
}, [dateRange])
return (
<svg className="Chart" />
)
}
看到将变量保持同步的方式简单多了是吗?不仅仅是代码量减少了!
类组成
class Chart extends Component {
state = {
data: null,
}
componentDidMount() {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
componentDidUpdate(prevProps) {
if (prevProps.dateRange != this.props.dateRange) {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
}
render() {
return (
<svg className="Chart" />
)
}
}
功能组成
实际上,最后一个示例仍在类组件中。我们存储data是state为了防止每次组件更新时重新计算它。
但是我们不再需要使用state!而是使用useMemo(),仅data当其依赖项数组更改时才会重新计算。
const Chart = ({ dateRange }) => {
const data = useMemo(() => (
getDataWithinRange(dateRange)
), [dateRange])
return (
<svg className="Chart" />
)
}
❝注意,我们可以只在函数内部定义变量。
❞
const Chart = ({ dateRange }) => {
const newData = getDataWithinRange(dateRange)
return (
<svg className="Chart" />
)
}
除非我们的getDataWithinRange()函数在计算上消耗很大,否则这就是非常完美。我平时使用useMemo用的非常多,特别是在处理大型数据集时,以保持性能。
考虑使用context
想象一下,我们有许多需要计算的值,但是它们取决于不同的props。例如,我们需要计算:
-
我们 data在我们的dateRange变化 -
的 dimensions图表的时margins变化 -
我们 scales在我们的data变化
类组成
class Chart extends Component {
state = {
data: null,
dimensions: null,
xScale: null,
yScale: null,
}
componentDidMount() {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
this.setState({dimensions: getDimensions()})
this.setState({xScale: getXScale()})
this.setState({yScale: getYScale()})
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.dateRange != this.props.dateRange) {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
if (prevProps.margins != this.props.margins) {
this.setState({dimensions: getDimensions()})
}
if (prevState.data != this.state.data) {
this.setState({xScale: getXScale()})
this.setState({yScale: getYScale()})
}
}
render() {
return (
<svg className="Chart" />
)
}
}
功能组成
在我们的功能组件中,我们只需要简单的几句:
const Chart = ({ dateRange, margins }) => {
const data = useMemo(() => (
getDataWithinRange(dateRange)
), [dateRange])
const dimensions = useMemo(getDimensions, [margins])
const xScale = useMemo(getXScale, [data])
const yScale = useMemo(getYScale, [data])
return (
<svg className="Chart" />
)
}
在我们的示例中,我们的类组件为什么会笨拙?
这是因为我们有很多的声明代码来去同步变量props和state,但在功能组件,我们只注重什么保持同步。
平时尽可能使用useMemo()钩子-让我们来实现功能效果。
现在再来一个需求
好的!继续前进-如果我们scales需要根据dimensions图表的图表进行更改怎么办?
类组成
在我们的类组件中,我们需要比较我们prevState和current state
class Chart extends Component {
state = {
data: null,
dimensions: null,
xScale: null,
yScale: null,
}
componentDidMount() {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
this.setState({dimensions: getDimensions()})
this.setState({xScale: getXScale()})
this.setState({yScale: getYScale()})
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.dateRange != this.props.dateRange) {
const newData = getDataWithinRange(this.props.dateRange)
this.setState({data: newData})
}
if (prevProps.margins != this.props.margins) {
this.setState({dimensions: getDimensions()})
}
if (
prevState.data != this.state.data
|| prevState.dimensions != this.state.dimensions
) {
this.setState({xScale: getXScale()})
this.setState({yScale: getYScale()})
}
}
render() {
return (
<svg className="Chart" />
)
}
}
功能组成
const Chart = ({ dateRange, margins }) => {
const data = useMemo(() => (
getDataWithinRange(dateRange)
), [dateRange])
const dimensions = useMemo(getDimensions, [margins])
const xScale = useMemo(getXScale, [data, dimensions])
const yScale = useMemo(getYScale, [data, dimensions])
return (
<svg className="Chart" />
)
}
是不是爽歪歪!
保持代码简洁
较短的代码不一定更易于阅读,但是使我们的组件尽可能地整洁无疑是一个优势!
最后
关注我们添加【前端进阶技术群】,回复:【资料包】领取前端进阶资料包 在看、转发支持作者❤️
本文分享自微信公众号 - 前端人(FrontendPeople)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

