React函数式组件与类组件

Posted by zhykirby on January 9, 2020

前言

最近都在学习React、React-Router、Redux(包括umi、dva什么的),当然还有憋得我很难受的论文。相比起憋论文,我觉得写代码实在太有意思了。
话题跑远了,总之学了这段时间,对React的设计模式啊、Redux的使用啊(说来惭愧,我上手Redux还是从dva入的门)、React-Router的一些实践啊(其实相比起react-router-config配置静态路由写路由表,我其实更喜欢写哪里都有的动态路由)都有了一些心得。然后现在就准备把这些心得写一写,加深印象。
就先从函数式组件和类组件的孽缘说起吧~

函数式组件与类组件的不同

函数式组件与类组件的不同有很多,给人最直观的感受可能是写法不同。最核心的不同大概是设计理念不同,毕竟函数式只关注输入和输出。由此延伸出来,实际上最大的不同就是状态的不同(这个不好总结,还是看下面分析吧)。

异步过程中的生化感染

举个实际的例子,假设你现在是小A,你点击了小B的头像,跳转到了小B的空间,此时你的状态还是小A,你可以做的事就是给小B点赞、评论。
这是个很常见的场景,但是这里面包含了一堆异步过程,在异步过程中,我们绝对不希望状态发生改变——要是点了小B的头像,跳到他的空间你就变成了小B,啊,那也太恐怖了(十分不符合常理,跟生化感染似的)。
通常情况下,我们会希望异步函数的参数还是原来的那个值,这样就会得到我们所预期的结果(当然,也有我们想一直获取最新值的时候)。此时,用函数式组件是很好的选择。

React中的函数总是捕获他们的值

函数式里没有this这个东西(你一般只在写类组件时用到this),函数只关心当前的输入和输出。只要输入值确定,输出值就确定,不会改变。
这里说点题外话,对于函数式编程(Function Programing),我是结合AOP(面向切面编程) 理解的。每个函数处理部分功能,接受输入,固定输出,每个函数都能作为下一个函数的输入。这么一个切面上有很多函数,只要你给出输入,就能获得最终你想要的结果,而不用思考该怎么组合。函数只需要你告诉他怎么做,再给出参数就行,而不用手动去操作。
有点扯远了,说回正题。函数式组件传入props,那么此时(或者说本次渲染调用本次函数)函数里的props就是传入时的值,输入值固定。那么在本次渲染时,输出值也将是固定的,不会改变。
函数式组件被证明是一个好的选择,那么使用类组件呢?这两类组件到底有什么差异呢?

当你使用类组件……

在React里,propsstate都是immutable(不可变) 的,这是很好的一点。但是this是可变的,且是永远都在变化的。
因此类组件会存在延时后读取props值发生改变的情况。你要是想获取之前的props值,就得想想其他办法,比如:

const { user } = this.props;

...

setTimeout(() => this.update(user), 3000);

要么在构造函数里绑定方法(“危”),如:

constructor(props) {
    super(props);
    this.update = this.update.bind(this);
}

但是上面两种方法都是有缺陷的。
对第一种方法来说,代码变得冗长,而且要是调用的参数不止一个,或者需要调用state时,都得写出来。
对第二种方法来说,其实这是并不起效的(hhh)。 当我们从this.props里读取数据时已经太迟了,此时 读取的已经不是我们所需要使用的上下文了。
此时,我们需要一个闭包来解决问题。
大神告诉我们,尽量避免使用闭包——我们难以想象 一个可能会随着时间推移而变化的变量。但是在当前 情景,没有问题,因为在react中的props和state是 immutable的。

使用闭包是没有问题的,只要保证不变。

在每一次渲染时capture就不会改变:

render() {
    const props = this.props;
    ...
}

此时,所有写在render里的函数都将能使用当前capture的props,它并不会改变。
emmmm,尽管问题解决了,但是问题也没有解决。

你看,这个Class这么没用,不如我们……

你要是把所有函数都写在render里,那写个class有啥意义呢??
(人类迷惑行为)
既然我们不需要class了,那我们就把class删了吧 然后就变成了函数式组件(哈哈哈)。
(人类迷惑行为大赏)
总结下,就是:函数式组件捕获了渲染所使用的值,而类组件时刻在改变。

假如我就要最新的值呢?

拓展下,假如你想要在函数式组件里使用最新的值怎么办呢?
这个问题在类组件里并不是问题,因为this.props和this.state的this随时可变。
不过在函数式组件里,你可能得用上“ref”。
比如使用useRef这个hook:

const ref = useRef(null);

我自己有封装了个通用的钩子函数(大概各位也有):

function useCurrentValue<T>(value: any): React.RefObject<T> {
    const ref = React.useRef(null);
    ref.current = value;
    return ref;
}

这个钩子就是使用现在的值,因为这个还是挺通用的,我就把它包装了一下,扔utlis里了。
通常情况,我们应该避免在渲染期间使用refs,这很好理解: 因为refs是可变的,而我们希望渲染的结果是可预测的,而不是难以控制的。
但是假如我们想要特定值的最新值,而手动更新很烦人,我们可以用另一个hook来实现更新:

const [msg, setMsg] = useState('');
const latestMsg = useRef('');
useEffect(() => {
  latestMsg.curret = msg;
});

总结

终于到了总结部分了 = =
也没啥好总结,就随便说两句吧
(得亏我一直很讨厌写类的方式,所以写起函数式很开心,哈哈哈)
写代码时要认为任何值都可以随时更改,指不定什么时候就被坑了。
Over.