那么在跨端方案百花齐放的今天,比如现在最为人们所熟知的react native、flutter、electron等,他们之间有没有什么共同的特点,而我们又是否能够找到其中的本质,就是今天这篇文章想讲述的问题。
站在用户的角度思考问题,与客户深入沟通,找到绥化网站设计与绥化网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站制作、网站设计、企业官网、英文网站、手机端网站、网站推广、主机域名、网站空间、企业邮箱。业务覆盖绥化地区。
其实,浏览器本就是一个跨端实现方案,因为你只需要输入网址,就能在任何端的浏览器上打开你的网页。那么,如果我们把浏览器嵌入 app 中,再将地址栏等内容隐藏掉,是不是就能将我们的网页嵌入原生 app 了。而这个嵌入 app 的浏览器,我们把它称之为 webview ,所以只要某个端支持 webview ,那么它就能使用这种方案跨端。
同时这也是开发成本最小的一种方案,因为这实际上就是在写前端界面,和我们开发普通的网页并没有太大区别。
典型的代表是 react-native,它的开发语言选择了 js,使用的语法和 react 完全一致,其实也可以说它就是 react,这就是我们的框架层。而不同于一般 react 应用,它需要借助原生的能力来进行渲染,组件最终都会被渲染为原生组件,这可以给用户带来比较好的体验。
这种方案和上面的区别就是,它并没有直接借用原生能力去渲染组件,而是利用了更底层的渲染能力,自己去渲染组件。这种方式显然链路会比上述方案的链路跟短,那么性能也就会更好,同时在保证多端渲染一致性上也会比上一种方案更加可靠。这类框架的典型例子就是 flutter 。
众所周知,在最近几年有一个东西变得非常火爆:小程序,现在许多大厂都有一套自己的小程序实现,但相互之间还是有不小差异的,通常可以借助 taro ,remax 这类框架实现一套代码,多端运行的效果,这也算是一种另类的跨端,它的实现方式还是比较有意思的,我们后面会展开细讲。
是不是看见了一些非常熟悉的函数名,在上一讲的基本原理中已经提到过了,这里我们就不再赘述。同时再看一下FiberNode的结构,也和react的保持一致,只不过我们在js层是无法拿到真实结点的,所以stateNode只是一个代号。
比如某个时候,用户点击了屏幕上的一个按钮触发了一个点击事件,此时界面需要进行相应的更新操作。
我们上述说的通知,都是通过 bridge 实现的,bridge本身是用实现C++的,就像一座桥一样,将各个模块关联起来,整个通信是一个「异步」的过程。这样做好处就是各自之间不会有阻塞关系,比如 不会native thread因为js thread而阻塞渲染,给用户良好的体验。但是这种「异步」也存在一个比较明显的问题:因为通信过程花费的时间比较长,所以在一些时效性要求较高场景上体验较差。
比如长列表快速滚动的时候或者需要做一些跟手的动画,整个过程是这样的:
我们很容易看出,这是由rn的架构引出的问题,其实小程序的架构也会有这个问题,所以在rn和小程序上出现一些需要频繁通信的场景时,就会导致页面非常差,流畅度降低。那么如果想解决这个问题,势必要从架构上去进行修改。
那么既然我们知道了rn是如何实现的跨端,那么我们就可以来探究一下它本质上是在干什么。首先,跨端可以分为「逻辑跨端」和「渲染跨端」。
「逻辑跨端」通常通过 vm来实现,例如利用v8 引擎,我们就能在各个平台上运行我们的js 代码,实现「逻辑跨端」。
那么第二个问题就是「渲染跨端」,我们把业务代码的实现抽象为开发层,比如 react-native中我们写的 react 代码就属于开发层,再把具体要渲染的端称为渲染层。作为开发层来说,我一定知道我想要的ui长什么样,但是我没有能力去渲染到界面上,所以当我声明了一个组件之后,我们需要考虑的问题是如何把我想要什么告诉渲染层。
就像这样的关系,那么我们最直观的方式肯定是我能够实现一种通信方式,在开发层将消息通知到各个系统,再由各个系统自己去调用对应的 api 来实现最终的渲染。
function render() {
if(A) {
message.sendA('render', { type: 'View' })
}
if(B) {
message.sendB('render', { type: 'View' })
}
if(C) {
message.sendC('render', { type: 'View' })
}
}
比如这样,我就能通过判断平台来通知对应的端去渲染View组件。这一部分的工作就是跨端框架需要帮助我们做的,它可以把这一步放到 JS 层,也可以把这一步放到c++ 层。我们应该把这部分工作尽量往底层放,也就是我们可以对各个平台的 api 进行一层封装,上层只负责调用封装的 api,再由这一层封装层去调用真正的 api。因为这样可以复用更多的逻辑,否则像上文中我们在 JS层去发送消息给不同的平台,我们就需要在A\B\C三个平台写三个不同方法去渲染组件。
但是,归根结底就是,一定有一个地方是通过判断不同平台来调用具体实现,也就是下面这样
有一个地方会对系统进行判断,再通过某种通信方式通知到对应的端,最后执行真正的方法。其实,所有跨端相关操作其实都在做上图中的这些事情。所有的跨端也可以总结为下面这句话:
「我知道我想要什么,但是我没有能力去渲染,我要通知有能力渲染的人来帮助我渲染」
比如hybrid跨端方案中,webview其实就充当了桥接层的角色,createElement,appendChild等api就是给我们封装好的跨平台api,底层最终调用到了什么地方,又是如何渲染到界面上的细节都被屏蔽掉了。所以我们利用这些api就能很轻松的实现跨端开发,写一个网页,只要能够加载 webview 的地方,我们的代码就能跑在这个上面。
又比如flutter的方案通过研发一个自渲染的引擎来实现跨端,这种思路是不是相当于另外一个浏览器?但是不同的点在于 flutter 是一个非常新的东西,而 webview 需要遵循大量的 w3c规范和背负一堆历史包袱。flutter 并没有什么历史包袱,所以它能够从架构,设计的方面去做的更好更快,能够做更多的事情。
对于跨端来说,如何屏蔽好各端的细节至关重要,比如针对某个端特有的api如何处理,如何保证渲染细节上各个端始终保持一致。如果一个跨端框架能够让开发者的代码里面不出现 isIos、isAndroid的字眼,或者是为了兼容各种奇怪的渲染而产生的非常诡异的hack方式。那我认为它绝对是一个真正成功的框架。
但是按我经验而言,先后写过的 h5、rn、小程序,他们都没有真正做到这一点,所以项目里面会出现为了解决不同端不一致问题而出现的各种奇奇怪怪的代码。而这个问题其实也是非常难解决的,因为各端的差异还是比较大的,所以说很难去完全屏蔽这些细节。
比如说h5中磨人的垂直居中问题,我相信只要开发过移动端页面的都会遇见,就不用我多说了。
为什么大家其实本质上都是在干一件事情,却出现了这么多的解决方案?其实大家都觉得某些框架没能很好的解决某个问题,所以想自己去造一套。其中可能很多开发者最关心的就是性能问题,比如:
但是其实我们在选择框架的时候性能并不是唯一因素,开发体验、框架生态这些也都是关键因素,我个人感受是,目前rn的生态还是比其他的要好,所以在开发过程中你想要的东西基本都有。
ok,说了这么多,对于跨端部分的内容其实我想说的已经说的差不多了,还记得上文提到的 Taro、Uni-app 一类跨小程序方案么。为什么说它是另类的跨端,因为它其实并没有实际跨端,只是为了解决各个小程序语法之间不兼容的问题。但是它又确实是一个跨端解决方案,因为它符合 「write once, run everything。」
下面我们先来了解下小程序的背景。
小程序是各个app厂商对外开放的一种能力。通过厂商提供的框架,就能在他们的app中运行自己的小程序,借助各大app的流量来开展自己的业务。同时作为厂商如果能吸引到更多的人加入到开发者大军中来,也能给app带来给多的流量,这可以看作一个双赢的业务。那么最终呈现在app中的页面是以什么方式进行渲染的呢?其实还是通过webview,但是会嵌入一些原生的组件在里面以提供更好的用户体验,比如video组件其实并不是h5 video,而是native video。
那么到了这里,我们就可以来谈一谈关于小程序跨端的东西了。关于小程序跨端,核心并不是真正意义上的跨端,虽然小程序也做到了跨端,例如一份代码其实是可以跑在android和Ios上的,但是实际上这和hybrid跨端十分相似。
在这里我想说的其实是,市面上现在有非常多的小程序:字节小程序、百度小程序、微信小程序、支付宝小程序等等等等。虽然他们的dsl十分相似,但是终归还是有所不同,那么就意味着如果我想在多个app上去开展我的业务,我是否需要维护多套十分相似的代码?我又能否通过一套代码能够跑在各种小程序上?
想通过一套代码跑在多个小程序上,和想通过一套代码跑在多个端,这两件事到底是不是一件事呢?我们再回到这张图
这些平台是否可以对应上不同的小程序?
再回到那句话:「我知道我想要什么,但是我没有能力去渲染,我要通知有能力渲染的人来帮助我渲染。」
现在来理一下我们的需求:
那么从这句话来看:「我」代表了什么,「有能力渲染的人」又代表了什么?
第二个很容易对应上,「有能力渲染的人」就是小程序本身,只有它才能帮助我们把内容真正渲染到界面上。
而「我」又是什么呢?其实这个「我」可以是很多东西,不过这里我们的需求是想用react进行开发,所以我们回想一下第一讲中react的核心流程,当它拿到vdom的时候,是不是就已经知道【我想要什么】了?所以我们把react拿到vdom之前的流程搬过来,这样就能获取到「我知道我想要什么」的信息,但是「我没有能力去渲染」,因为这不是web,没有dom api,所以我需要通知小程序来帮助我渲染,我还可以根据不同的端来通知不同的小程序帮助我渲染。
所以整个流程就是下面这样的:
前面三个流程都在我们的js层,也就是开发层,我们写的代码经历一遍完整的 react 流程之后,会将最后的结果给到各个小程序,然后再走小程序自己的内部流程,将其真正的渲染到界面上。
采用这种做法的典型例子有remax、taro3,他们宣称用真正的react去开发小程序,其实并没有错,因为真的是把react的整套东西都搬了过来,和react并无差异。我们用taro写一个非常简单的例子来看一下:
import { Component } from 'react'
import { View, Text, Button } from '@tarojs/components'
import './index.css'
export default class Index extends Component {
state = {
random: Math.random()
}
componentWillMount () { }
componentDidMount () { }
componentWillUnmount () { }
componentDidShow () { }
componentDidHide () { }
handleClick = () => {
debugger;
console.log("Math.random()", Math.random());
this.setState({random: Math.random()})
}
render () {
return (
Hello world! {this.state.random}
)
}
}
这是一个用taro写的组件,把它编译到字节小程序之后是这样的效果:
根据我们之前的分析,在最后生成的文件中,一定包含了一个「小程序渲染器」。它接受的data就是整个ui结构,然后通过小程序的渲染能力渲染到界面上,我们去dist文件中找一下,就能找到一个base.ttml的文件,里面的内容是这样的
{{i.v}}
从名字可以看出,这是用于渲染各种组件的template,所以当我们拿到react传递过来的data时,将其传给template,template就能根据对应的组件名采用不同的模版进行渲染。随后再用一个for循环将其子组件进行递归渲染,完成整个页面的渲染。这个就可以理解为我们针对不同端写的不同渲染器,如果我们编译到wx小程序,这里面的内容是会不同的。
总之,「在」**react**「对其处理完之后,会把数据」**setData**「传递给「「小程序」」,小程序再用之前写好的各种」**template**「将其渲染到页面上。」
下面这张图就是经过react处理之后,能够拿到页面的数据,将其传递给小程序之后,就能递归渲染出来。
那么这样的架构有什么问题呢,可以很明显的看到会走两遍diff,为什么会走两遍diff呢?因为在react层为了获取到我想要什么这个信息,我们必须走一遍diff,这样才能将最后得到的data交给小程序。
而交给小程序之后,小程序对于之前的流程是无感知的,所以它为了得到需要更新什么这个信息,也需要过一遍diff,或者通过一些其他的方式来拿到这个信息(并没有深入了解过小程序的渲染流程,所以不确定是否是通过diff拿到的),所以这一整套流程就会走两遍diff。
为什么我们不能将两次diff合并为一次?因为小程序的渲染对开发者而言就是个黑盒,我们不能干扰到其内部流程。如果我们能够直接对接小程序的渲染sdk,那么其实根本没必要走两遍diff,因为前置的 react的diff我们已经能够知道需要更新什么内容。
这个问题的本质和普通意义上的跨端框架没有太大的区别,开发层也就是 react 知道自己需要什么东西,但是它没有能力去渲染到界面上,所以需要通过小程序充当渲染层来渲染到真正的界面上。这种开发方式有一种用 react 去写 vue 的意思,但是为什么会出现这种诡异的开发方式,如果这个 vue 做的足够好的话,谁又想去这样折腾?
其实还有一个小问题,wx的template是无法支持递归调用的,也就导致了我们想用template递归渲染data内容是无法实现的,那么这个问题要如何解决呢..我们看一下上面的代码在wx小程序中编译出来的结果:
我们可以看到各种template之间多了0、1、2、3这种标号..就是为了解决无法递归调用的问题,提前多造几个名字不同功能相同的template,不就能跨过递归调用的限制了么...
上述的这些跨端都是通过某种架构方式去实现的,那如果我们粗暴一点的想,我能不能直接把一套代码通过编译的方式去编译到不同的平台。比如我把js代码编译成java代码、object-c代码,其实,个人感觉也不是不行,但是因为这些的差异实在太大,所以在写js代码的时候,可能需要非常强的约束性、规范性,把开发者限制在某个区域内,才能很好的编译过去。也就是说,从js到java其实是一个自由度
本文名称:聊聊跨端技术的本质与现状
本文网址:http://www.gawzjz.com/qtweb/news47/172647.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联