React 实现 Vue 中 watch 和 computed
计算属性 computed 和数据监听 watch 两者的区别
- 计算属性适合做值得转换,基于一个值生成另一个值
- 数据监听更适合一些动作响应,即监听某个值变化后执行相应动作
React Class Component 实现 Vue 的 computed (计算属性)
对于类组件,直接使用 get 方法即可实现计算属性 computed:
React 中的 getter 并没有做缓存优化,我们需要在项目中引入 memoize-one 库实现缓存效果
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
| import React, { Component } from 'react';
export default class demo extends Component { constructor(props) { super(props); this.state = { value: 1 }; } get computedValue1 () { return this.state.value + 10; } get computedValue2 () { return this.computedValue1 * 2; } add = () => { this.setState({ value: this.state.value + 1 }) } render () { return ( <div> <div>原state: {this.state.value}</div> <div>计算属性1 + 10: {this.computedValue1}</div> <div>计算属性1 = 计算属性1 * 2: {this.computedValue2}</div> <button onClick={this.add}>点击加一</button> </div> ); } }
|
React Class Component 实现 Vue 的 watch (数据监听)
对于类组件,使用生命周期 componentDidUpdate 即可实现数据监听:
componentDidUpdate 在首次渲染时不会执行,在组件更新时立即调用,可以在这时候执行 setState 但需要把 setState 放在条件语句中,否则会陷入更新无限循环。通过这个钩子实现对 state/props/getter 的监听 从而触发相应动作。
依赖全局某些需要异步获取的状态时,通常有两种用途:
直接渲染。当状态改变时会自动触发重新渲染,直接放在 componentDidMount 中即可
根据状态的值做相应的动作,如发起接口请求,需要监听此状态,当它有值再执行相应操作
案例:在项目初始化时,获取接口数据 “用户信息”,属于异步操作,之后存储在全局状态 redux 中,某页面通过 connect 获取全局状态的 “用户信息”,并根据该 “用户信息” 请求一个专属于该用户的报告列表并渲染。
分析:“用户信息” 是通过接口获取的,属于异步操作,在报告页面需要监听获取到的 “用户信息”,当它有值时再发起 “用户报告” 的请求,所以不能写在 componentDidMount 中,此时,需要在类组件中使用 componentDidUpdate 监听状态数据:
1 2 3 4 5
| componentDidUpdate (prevProps, prevState){ if(isEmpty(prevProps.xxx) && !isEmpty(this.props.xxx)) { } }
|
监听上述计算属性 demo 的 state 变化,在 value 超过 10 时在控制台输出 “超过 10 了”
1 2 3 4 5 6
| componentDidUpdate () { if(this.state.value > 10) { console.log('超过10了') } }
|
React Function Component 实现 Vue 的 computed (计算属性)
在函数式组件中,使用 useMemo 这个 hooks 实现,有缓存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { useMemo, useState } from 'react'
export default function Demo1 () { const [count, setCount] = useState(0)
const double = useMemo(() => { console.log('double') return count * 2 }, [count])
return ( <div> <button onClick={() => { setCount(count + 1) }} > 点击+1 </button> <div>Count is :{count}</div> <div>Double is :{double}</div> </div> ) }
|
React Function Component 实现 Vue 的 watch (数据监听)
使用 useEffect 和 useRef 自定义一个叫 useWatch 的 hooks,useEffect 实现基本监听功能,useRef 实现 Vue watch 中的 oldValue/immediate/stop:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| import React, { useMemo, useState, useEffect, useRef } from 'react'
type Callback<T> = (prev: T | undefined) => void;
type Config = { immediate: boolean; };
function useWatch<T>(dep: T, callback: Callback<T>, config: Config = { immediate: false }) { const { immediate } = config;
const prev = useRef<T>(); const inited = useRef(false); const stop = useRef(false);
useEffect(() => { const execute = () => callback(prev.current);
if (!stop.current) { if (!inited.current) { inited.current = true; if (immediate) { execute(); } } else { execute(); } prev.current = dep; } }, [dep]);
return () => { stop.current = true; }; }
const App: React.FC = () => { const [prev, setPrev] = useState() const [count, setCount] = useState(0);
const stop = useWatch(count, (prevCount) => { console.log('prevCount: ', prevCount); console.log('currentCount: ', count); setPrev(prevCount) })
const add = () => setCount(prevCount => prevCount + 1)
return ( <div> <p> 当前的count是{count}</p> <p> 前一次的count是{prev}</p> {count} <button onClick={add} className="btn">+</button> <button onClick={stop} className="btn">停止观察旧值</button> </div> ) }
export default App;
|