Nicksites Techblog

  组件判断是否更新,因为是数据的浅比较,所以所需对象引用变了,则该组件重新渲染

  v5.0 之前存在着一个问题:父(祖先)子(孙)组件都订阅,子(孙)组件可能受到父(祖先)组件渲染影响,而导致多次渲染。v5.0 之后发布/订阅模块重写,解决了之前的问题,主要特点:增加层级(嵌套)观察者,保证事件通知与组件更新的顺序

  通过context传递subscription,组件订阅前检查,如果父(祖先)组件已经订阅,则将子组件的回调函数stateChange订阅到父(向上找到最开始的祖先)组件的观察者,否则订阅到redux store(上面的trySubscribe方法)

  父(祖先)组件setState明明可能会让子(孙)组件更新,为什么还需要通知一次?

  必要性:因为存在这样的场景:子组件可能不更新(ShouldComponentUpdate返回false),则需要更新的孙子组件没有受setState影响,为了保证所有订阅的组件都能得到数据更新,所以需要通知

  无副作用:假设子组件因为父组件render而重新render,因为通知是在父组件componentDidUpdate方法触发,而子组件因为已经渲染过,数据得到了更新,所以就算再收到了通知也不会再渲染了

  Aniamted组件是React-Native官方出品的动画组件。在本文开始之前,我们首先来看看来自官网的一个例子,看看如何使用Animated组件。

  这个例子展示了如何写一个FadeInView动画组件,呈现了在10s内驱动一个组件的透明度从0到1渐变的过程。

  本文分析篇幅仅限不使用原生动画驱动的场景,原生动画驱动使用了NativeAnimatedModule,鉴于笔者没有相关原生开发经验,在此不展开。不过据笔者阅读源码所知:原生动画驱动并不会驱动组件重复渲染,而是直接修改相关属性。

  我们先来了解一下,作为动画过程的关键变化值Animated.Value。

  flush函数通过向上递归遍历找到最近的使用该value,并且拥有update方法的实例对象,并触发该类的update方法。

  所以:执行update方法,便会触发从动画组件Animated.View传过来的回调函数,在不使用原生动画驱动的情况下,触发forceUpdate,使组件重新渲染。

  对于使用了继承自AnimatedNode的类实例,该实例会将当前AnimatedProps实例对象,添加到_children数组中寄存,便于之后使用。

  在例子中以及经过上面的代码分析,我们得知:AnimatedValue的实例对象交给了AnimatedStyle。

  不像AnimatedProps,在AnimatedStyle类的构造函数中,并没有调用__attach方法。通过观察函数调用栈,我们发现了端倪:

  Animated.View组件在初始化渲染的同时,使得Animated.Value保存了调用自身的类实例对象。

  timing.start动画开始执行,Animated.Value会以一定时间规律触发自身值改变,并在改变的同时,触发flush函数,通过向上遍历调用实例的update方法。

  在不使用原生动画驱动的场景下,Animated.View会在动画函数根据缓动方式的更新时机触发在requestAnimationFrame包裹下的forceUpdate,使组件持续更新并拿到最新的style。

  预编译的使用场景在于,一些文本转换或格式化,通常模板固定,数据动态变化的情况。通过预编译技术,将重复使用的模板预先编译,避免了实时编译转换,以达到提升性能的目的。

  预编译的结果,得到的是对模板进行词法分析而得到的一系列标记 (tokens)。对数据进行渲染 (执行render函数)时,其实是通过遍历tokens再组合数据,生成结果。

  “转换”意思是将小程序不支持的东西转换成它支持的东西。我在开发的小程序的过程中遇到了两种需要做“转换”的场景:

  我们的产品在某些场景下,后端接口会直接传html字符串给前端。在ReactJs中,我们可以用dangerouslySetInnerHTML直接渲染html字符串(不一定安全),而 ”小程序“不支持html,因此必须对html进行处理。解决这个问题的步骤主要是:1. 将html转换成json( 树结构) ;2. 将json转换成wxml。我在对问题做了调研后发现,现有一个库wxParse满足该转换的目的,但是在我看来,这个库做的事情太多,需要依赖文件过多,不满足只需要简单处理的需要,所以我决定自己写。

  上面的lexer将html字符串分隔成了一个一个token,然后,我们通过遍历所有的标识来构建树

  在熟悉了“小程序”框架的基础上,发现需要借助模板template,将json数据填充进template,并根据元素类型渲染相应的wxml组件以达到转换目的。比如:

  而因为模板没有引用自身的能力,只能使用笨办法:使用多个同样内容,但是模板名称不一样的模板来解决嵌套的层级关系,而嵌套的层级取决于使用的模板个数。

  如上处理过程中,有些需要注意的细节,比如:要对html实体字符转换,让模板的image组件支持mode等等。总之,经过如上的处理,html字符串对wxml组件的转换基本功能完成。

  在我们的产品web版本中,由于需要在页面元素中使用svg作为dom元素,而“小程序” 没有svg组件的支持,如此一来,我们也需要对后端接口传来的svg字符串做转换。“小程序”没有svg组件但是有canvas组件,于是我决定使用canvas来模拟svg绘制图形,并将图形做一定的修改以满足基本需求。

  做这个“转换”的关键也有两点:1. 提取svg字符串中的元素;2.canvas模拟元素功能进行绘制

  因为svg字符串是一个xml, 用上面的htmlParser可以将其生成json,问题解决。

  在web中svg的元素有很多,好在我们需要的只有一些基本的元素:image,rect,path。rect用canvas模拟不算难事,canvas绘制起来很简单,代码如下:

  然而,在开发过程中,遇到了一个难点:不知道对path的d属性如何进行模拟。d属性涉及移动、贝塞尔曲线等等。比如:path d=M250 150 L150 350 L350 350 Z /这个例子定义了一条路径,它开始于位置 250 150,到达位置 150 350,然后从那里开始到 350 350,最后在 250 150 关闭路径(M = moveto,L = lineto,Z = closepath)。用canvas进行绘制,需要先提取d各属性值,再依据各属性值与canvas的对应绘制关系依次进行绘制。在我为此犯难的时候,好在发现有前人有做了这样事情。在gist上,发现了对d的解析,与canvas绘制d的相关代码,困难问题也得以解决。

  完成了如上的步骤后,图形基本绘制出来了,但是在后期,出现了svgimage位置的问题。svg中的图片除了会有x,y坐标关系,还会根据视窗大小,以短边为准,保持宽高比,长边做缩放,视窗中居中显示。这是我之前不清楚的部分,为此多花了点时间和精力。此外,还有些细节需要注意,比如需要调整canvas的缩放比例,以让图形完全显示。

  以上,就是我在开发“小程序”中对html与svg做的一些“转换”的经历。总结起来就是,对字符串解析,转换成“小程序”语言。在此延伸一下,如需在wxml中支持wxml字符串,借助htmlParser做解析,再写一个wxml模板,我们也就能“转换”wxml了。

  Redux的中间件是定义一个函数,对dispatch进行改造,在发出action与执行reducer之间添加其他功能,这是对Redux进行功能拓展的方式。

相关阅读