博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React Ref or Not?
阅读量:6935 次
发布时间:2019-06-27

本文共 3636 字,大约阅读时间需要 12 分钟。

欢迎关注我的公众号睿Talk,获取我最新的文章:

clipboard.png

一、前言

React的Ref特性是React声明式编程(Declarative Programming)设计哲学的一个重要补充。之前对它的认识只是停留在非受控组件这种特殊场景,直到最近为了实现项目中的一个特殊功能,才对它有了更深的理解。

二、什么是Ref

React的官方解释是这样的:

In the typical React dataflow, props are the only way that parent components interact with their children. To modify a child, you re-render it with new props. However, there are a few cases where you need to imperatively modify a child outside of the typical dataflow. The child to be modified could be an instance of a React component, or it could be a DOM element. For both of these cases, React provides an escape hatch.

当中提到了几个关键的概念。

  • 在典型的React数据流理念中,父组件跟子组件的交互都是通过传递属性(properties)实现的。如果父组件需要修改子组件,只需要将新的属性传递给子组件,由子组件来实现具体的绘制逻辑。
  • 特殊的情况下,如果你需要命令式(imperatively)的修改子组件,React也提供了应急的处理办法--Ref
  • Ref既支持修改DOM元素,也支持修改自定义的组件。

三、什么是声明式编程(Declarative Programming)

值得一提的是当中声明式编程(Declarative Programming)和命令式编程(Imperative Programming)的区别。声明式编程的特点是只描述要实现的结果,而不关心如何一步一步实现的,而命令式编程则相反,必须每个步骤都写清楚。以数组为例,如果要打印出数组所有元素,声明式编程是这么写:

let arr = [1,2,3];const printElement = (element) => console.log(element);arr.forEach( printElement );

而用命令式编程,会这么写:

let arr = [1,2,3];for (let i = 0; i < arr.length; i++) {    console.log(arr[i]);}

通过对比,我们可以很直观的感受到声明式编程的好处。代码的核心功能就是这句:

arr.forEach( printElement );

我们可以根据语义直观的理解代码的功能是:针对数组的每一个元素,将它的值打印出来。不必关心实现其的细节。而命令式编程必须将每行代码读懂,然后再整合起来理解总体实现的功能。

React有2个基石设计理念:一个是声明式编程,一个是函数式编程。函数式编程以后有机会再展开讲。声明式编程的特点体现在2方面:

  • 组件定义的时候,所有的实现逻辑都封装在组件的内部,通过state管理,对外只暴露属性。
  • 组件使用的时候,组件调用者通过传入不同属性的值来达到展现不同内容的效果。一切效果都是事先定义好的,至于效果是怎么实现的,组件调用者不需要关心。

因此,在使用React的时候,一般很少需要用到Ref。那么,Ref的使用场景又是什么?

四、Ref使用场景

React官方文档是这么说的:

There are a few good use cases for refs:

  • Managing focus, text selection, or media playback.
  • Triggering imperative animations.
  • Integrating with third-party DOM libraries.

Avoid using refs for anything that can be done declaratively.

意思是:

  • 控制一些DOM原生的效果,如输入框的聚焦效果和选中效果等;
  • 触发一些命令式的动画;
  • 集成第三方的DOM库。

最后还补了一句:如果要实现的功能可以通过声明式的方式实现,就不要借助Ref。如果你就是那么任性,要使用Ref,具体该怎么做?

五、Ref用法

  • 如果作用在原生的DOM元素上,通过Ref获取的是DOM元素,可以直接操作DOM的API:
class CustomTextInput extends React.Component {  constructor(props) {    super(props);    this.focusTextInput = this.focusTextInput.bind(this);  }  focusTextInput() {    // 获取DOM元素后可以直接操作DOM API    this.textInput.focus();  }  render() {    // 通过Ref获取DOM元素,再保存在实例变量focusTextInput中    return (      
{ this.textInput = input; }} />
); }}
  • 如果作用在自定义组件,Ref获取的是组件的实例,可以直接操作组件内的任意方法:
// CustomTextInput组件的定义跟上面完全相同class AutoFocusTextInput extends React.Component {  componentDidMount() {    // 这里直接调用CustomTextInput实例的focusTextInput方法    this.textInput.focusTextInput();  }  render() {    return (      // 通过Ref获取CustomTextInput实例,再保存在实例变量textInput中      
{ this.textInput = input; }} /> ); }}

理解了基本使用后,再回到我遇到的真实场景。

六、Ref应用

先简单描述下项目要实现的效果:在一个页面中分左右两部分,左边显示商品的列表,右边显示选中商品的购物车。一次可以将左边的多个商品,添加到右边的购物车中。由于具体的实现细节比较复杂,当时的分工是一个人实现左侧的商品列表,另一人负责右边的购物车。如果用传统的React设计理念来实现,必须要借助左边列表组件和右边购物车组件的共同父组件,也就是页面的根组件,来维护选中的商品数组。然后再将商品数组传入购物车展示。这样做的话实现起来非常不方便,要把购物车中的很多逻辑都放在父组件中,而实际上这些逻辑是购物车自己独立使用的,跟其它组件并没有交互。左侧的列表组件只需要将选中的商品告知购物车即可,后续的逻辑由购物车自己实现。

考虑再三后,我们决定通过Ref的方式将其内部的addProduct的方法暴露出来给父组件,当选中一个商品后,列表组件将商品信息传递给父组件,父组件再通过addProduct方法将商品信息传入购物车。由购物车组件自己来维护客户购买的所有商品数据。整体逻辑就是这样,具体代码此处略过,主要描述的是思路。也许你会问为啥不将商品信息通过props传入购物车组件?实现上是没问题的,都能达到效果。但我们认为显式的调用addProduct方法会更加直观的表达语义,同时对addProduct方法也做了限制,只负责添加商品信息,不做更多的逻辑判断。

如果说还有没更好的实现方式,其实是有的,可以通过Redux来管理整个页面的状态。但引入Redux后,代码的维护成本会随之上升,目前暂时不作考虑。

七、总结

本文以项目中遇到的设计问题为起点,介绍了React Ref特性的使用场景和具体的使用方法,顺便还对比了声明式编程和命令式编程2种编程风格,对React的设计理念作了简要的解读。

八、后记

这篇文章是八个月前写的,随着业务的快速发展,当初写的代码无法承载新业务,要全盘重构了。重构的设计中,我们引入了Redux来做状态管理,组件之间的耦合度一下子就下降了很多,复杂业务的实现也变得容易了。所以我认为最优的实现方案是使用Redux,而不是Ref。

转载地址:http://xswnl.baihongyu.com/

你可能感兴趣的文章
Hadoop之Storm命令
查看>>
模板的那些事
查看>>
如何去设计一个自适应的网页设计或HTMl5
查看>>
Ajax方式上传文件报错"Uncaught TypeError: Illegal invocation"
查看>>
TextVew中文空格
查看>>
android touch screen keyboard input移植记录
查看>>
linux 手动释放buff/cache
查看>>
java操作elasticsearch实现query String
查看>>
iOS开发 - OC - block的详解 - 深入篇
查看>>
保利入驻宜昌 外来大咖谁更靠谱
查看>>
11.15日工作总结(补)
查看>>
(转)Spring读书笔记-----Spring的Bean之Bean的基本概念
查看>>
Python_面向对象_类1
查看>>
设计模式-简单工厂模式
查看>>
公开SNS社区即时找朋友链的源代码和部署方案(续四)
查看>>
[TJOI2019]甲苯先生的滚榜——非旋转treap
查看>>
String、Brush、Color 相互转换
查看>>
项目管理初探
查看>>
如何为ListView中的ImageView添加动画,谢谢!
查看>>
mvc
查看>>