最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Taro(三)

    正文概述 掘金(yolkpie)   2021-03-05   896

    七、小程序运行时

    为了使 Taro 组件转换成小程序组件并运行在小程序环境下, Taro 主要做了两个方面的工作:编译以及运行时适配。编译过程会做很多工作,例如:将 JSX 转换成小程序 .wxml 模板,生成小程序的配置文件、页面及组件的代码等等。编译生成好的代码仍然不能直接运行在小程序环境里,那运行时又是如何与之协同工作的呢?…

    7.1 注册程序、页面以及自定义组件

    在小程序中会区分程序、页面以及组件,通过调用对应的函数,并传入包含生命周期回调、事件处理函数等配置内容的 object 参数来进行注册:

    Component({
      data: {},
      methods: {
        handleClick() {},
      },
    });
    

    而在 Taro 里,它们都是一个组件类:

    class CustomComponent extends Component {
      state = { }
      handleClick () { }
    }...
    
    • 那么 Taro 的组件类是如何转换成小程序的程序、页面或组件的呢?
    • 例如,有一个组件:customComponent,编译过程会在组件底部添加一行这样的代码(此处代码作示例用,与实际项目生成的代码不尽相同):
    Component(createComponent(customComponent));
    
    • createComponent 方法是整个运行时的入口,在运行的时候,会根据传入的组件类,返回一个组件的配置对象

    createComponent 方法主要做了这样几件事情:

    • 将组件的 state 转换成小程序组件配置对象的 data
    • 将组件的生命周期对应到小程序组件的生命周期
    • 将组件的事件处理函数对应到小程序的事件处理函数

    7.2 组件 state 转换

    其实在 Taro(React) 组件里,除了组件的 state,JSX 里还可以访问 props 、render 函数里定义的值、以及任何作用域上的成员。而在小程序中,与模板绑定的数据均来自对应页面(或组件)的 data。因此 JSX 模板里访问到的数据都会对应到小程序组件的 data 上。接下来我们通过列表渲染的例子来说明 state 和 data 是如何对应的…

    在 JSX 里访问 state

    {
      state = {
        list: [1, 2, 3]
      }
      render () {
        return (
          <View>
            {this.state.list.map(item => <View>{item}</View>)}
          </View>
        )
      }
    }
    

    编译后的小程序组件模板:

    <view>
      <view wx:for="{{list}}" wx:for-item="item">
        {{ item }}
      </view>
    </view>
    

    其中 state.list 只需直接对应到小程序(页面)组件的 data.list 上即可…

    在 render 里生成了新的变量

    然而事情通常没有那么简单,在 Taro 里也可以这么用

    {
      state = {
        list = [1, 2, 3]
      }
      render () {
        return (
          <View>
            {this.state.list.map(item => ++item).map(item => <View>{item}</View>)}
          </View>
        )
      }
    }
    

    编译后的小程序组件模板是这样的:

    <view>
      <view wx:for="{{$anonymousCallee_1}}" wx:for-item="item">{{item}}</view>
    </view>...
    
    var $anonymousCallee_1 = this.state.list.map(function(item) {
      return ++item;
    });
    

    7.3 将组件的生命周期对应到小程序组件的生命周期

    Taro(三)

    小程序页面的 componentWillMount 有一点特殊,会有两种初始化方式。由于小程序的页面需要等到 onLoad 之后才可以获取到页面的路由参数,因此如果是启动页面,会等到 onLoad 时才会触发。而对于小程序内部通过 navigateTo 等 API 跳转的页面,Taro 做了一个兼容,调用 navigateTo 时将页面参数存储在一个全局对象中,在页面 attached 的时候从全局对象里取到,这样就不用等到页面 onLoad 即可获取到路由参数,触发 componentWillMount 生命周期…

    状态更新

    Taro(三)

    • Taro 组件的 setState 行为最终会对应到小程序的 setData。Taro 引入了如 nextTick ,编译时识别模板中用到的数据,在 setData 前进行数据差异比较等方式来提高 setState 的性能。
    • 如上图,组件调用 setState 方法之后,并不会立刻执行组件更新逻辑,而是会将最新的 state 暂存入一个数组中,等 nextTick 回调时才会计算最新的 state 进行组件更新。这样即使连续多次的调用 setState 并不会触发多次的视图更新。在小程序中 nextTick 是这么实现的…
    const nextTick = (fn, ...args) => {
      fn = typeof fn === 'function' ? fn.bind(null, ...args) : fn
      const timerFunc = wx.nextTick ? wx.nextTick : setTimeout
      timerFunc(fn)
    }...
    

    除了计算出最新的组件 state ,在组件状态更新过程里还会调用前面提到过的 _createData 方法,得到最终小程序组件的 data,并调用小程序的 setData 方法来进行组件的更新

    7.4 事件处理函数对应

    在小程序的组件里,事件响应函数需要配置在 methods 字段里。而在 JSX 里,事件是这样绑定的:

    <View onClick={this.handleClick}></View>
    

    编译的过程会将 JSX 转换成小程序模板:

    <view bindclick="handleClick"></view>...
    

    在 createComponent 方法里,会将事件响应函数 handleClick 添加到 methods 字段中,并且在响应函数里调用真正的 this.handleClick 方法。

    在编译过程中,会提取模板中绑定过的方法,并存到组件的 $events 字段里,这样在运行时就可以只将用到的事件响应函数配置到小程序组件的 methods 字段中。

    在运行时通过 processEvent 这个方法来处理事件的对应,省略掉处理过程,就是这样的…

    function processEvent(eventHandlerName, obj) {
      obj[eventHandlerName] = function(event) {
        // ...
        scope[eventHandlerName].apply(callScope, realArgs);
      };
    }
    

    这个方法的核心作用就是解析出事件响应函数执行时真正的作用域 callScope 以及传入的参数。在 JSX 里,我们可以像下面这样通过 bind 传入参数:

    <View onClick={this.handleClick.bind(this, arga, argb)}></View>
    

    小程序不支持通过 bind 的方式传入参数,但是小程序可以用 data 开头的方式,将数据传递到 event.currentTarget.dataset 中。编译过程会将 bind 方式传递的参数对应到 dataset 中,processEvent 函数会从 dataset 里取到传入的参数传给真正的事件响应函数。

    至此,经过编译之后的 Taro 组件终于可以运行在小程序环境里了…

    7.5 对 API 进行 Promise 化的处理

    Taro['getStorage'] = options => {
      let obj = Object.assign({}, options)
      const p = new Promise((resolve, reject) => {
    	['fail', 'success', 'complete'].forEach((k) => {
    	  obj[k] = (res) => {
    	    options[k] && options[k](res)
    	    if (k === 'success') {
    		  resolve(res)
    	    } else if (k === 'fail') {
    		  reject(res)
    	    }
    	  }
    	})
    	wx['getStorage'](obj)
      })
      return p
    }...
    

    就可以这么调用了:

    // 小程序的调用方式
    Taro.getStorage({
      key: 'test',
      success() {
    
      }
    })
    // 在 Taro 里也可以这样调用
    Taro.getStorage({
      key: 'test'
    }).then(() => {
      // success
    })...
    

    八、H5 运行时

    8.1 H5 运行时解析

    使用 Taro 之后,我们书写的是类似于下图的代码…

    Taro(三)

    我们注意到,就算是转换过的代码,也依然存在着 view、button 等在 Web 开发中并不存在的组件。如何在 Web 端正常使用这些组件?这是我们碰到的第一个问题

    8.1.1 组件实现

    Taro(三)

    作为开发者,你第一反应或许会尝试在编译阶段下功夫,尝试直接使用效果类似的 Web 组件替代:用 div 替代 view,用 img 替代 image,以此类推。

    费劲心机搞定标签转换之后,上面这个差异似乎是解决了。但很快你就会碰到一些更加棘手的问题:hover-start-time、hover-stay-time 等等这些常规 Web 开发中并不存在的属性要如何处理?

    回顾一下:在前面讲到多端转换的时候,我们说到了 babel。在 Taro 中,我们使用 babylon 生成 AST,babel-traverse 去修改和移动 AST 中的节点。但 babel 所做的工作远远不止这些。

    我们不妨去 babel 的 playground 看一看代码在转译前后的对比:在使用了@babel/preset-env 的 BUILT-INS 之后,简单的一句源码 new Map(),在 babel 编译后却变成了好几行代码…

    Taro(三)

    注意看这几个文件:core-js/modules/web.dom.iterable,core-js/modules/es6.array.iterator,core-js/modules/es6.map。我们可以在 core-js 的 Git 仓库找到他们的真身。很明显,这几个模块就是对应的 es 特性运行时的实现。

    从某种角度上讲,我们要做的事情和 babel 非常像。babel 把基于新版 ECMAScript 规范的代码转换为基于旧 ECMAScript 规范的代码,而 Taro 希望把基于 React 语法的代码转换为小程序的语法。我们从 babel 受到了启发:既然 babel 可以通过运行时框架来实现新特性,那我们也同样可以通过运行时代码,实现上面这些 Web 开发中不存在的功能。

    举个例子。对于 view 组件,首先它是个普通的类 React 组件,它把它的子组件如实展示出来…

    import Nerv, { Component } from 'nervjs';
    
    class View extends Component {
      render() {
        return (
          <div>{this.props.children}</div>
        );
      }
    }...
    
    // 示例代码
    render() {
      const {
        hoverStartTime = 50,
        onTouchStart
      } = this.props;
    
      const _onTouchStart = e => {
        setTimeout(() => {
          // @TODO 触发touch样式改变
        }, hoverStartTime);
        onTouchStart && onTouchStart(e);
      }
      return (
        <div onTouchStart={_onTouchStart}>
          {this.props.children}
        </div>
      );
    }...
    

    再稍加修饰,我们就能得到一个功能完整的 Web 版 View 组件

    view 可以说是小程序最简单的组件之一了。text 的实现甚至比上面的代码还要简单得多。但这并不说明组件的实现之路上就没有障碍。复杂如 swiper,scroll-view,tabbar,我们需要花费大量的精力分析小程序原生组件的 API,交互行为,极端值处理,接受的属性等等,再通过 Web 技术实现。…

    8.2 API 适配

    Taro(三)

    小程序的 API 实现是个巨大的黑盒,我们仅仅知道如何使用它,使用它会得到什么结果,但对它内部的实现一无所知。

    如何让 Web 端也能使用小程序框架中提供的这些功能?既然已经知道这个黑盒的入参出参情况,那我们自己打造一个黑盒就好了。

    换句话说,我们依然通过运行时框架来实现这些 Web 端不存在的能力。

    具体说来,我们同样需要分析小程序原生 API,最后通过 Web 技术实现。有兴趣可以在 Git 仓库中看到这些原生 API 的实现。下面以 wx.setStorage 为例进行简单解析。

    wx.setStorage 是一个异步接口,可以把 key: value 数据存储在本地缓存。很容易联想到,在 Web 开发中也有类似的数据存储概念,这就是 localStorage。到这里,我们的目标已经十分明确:我们需要借助于 localStorage,实现一个与 wx.setStorage 相同的 API。…

    /* 示例代码 */
    function setStorage({ key, value }) {
      localStorage.setItem(key, value);
    }
    

    我们顺手做点优化,把基于异步回调的 API 都给做了一层 Promise 包装,这可以让代码的流程处理更加方便。所以这段代码看起来会像下面这样:

    /* 示例代码 */
    function setStorage({ key, value }) {
      localStorage.setItem(key, value);
      return Promise.resolve({ errMsg: 'setStorage:ok' });
    }...
    

    看起来很完美,但开发的道路不会如此平坦。我们还需要处理其余的入参:success、fail 和 complete。success 回调会在操作成功完成时调用,fail 会在操作失败的时候执行,complete 则无论如何都会执行。setStorage 函数只会在 key 值是 String 类型时有正确的行为,所以我们为这个函数添加了一个简单的类型判断,并在异常情况下执行 fail 回调。经过这轮变动,这段代码看起来会像下面这样…

    /* 示例代码 */
    function setStorage({ key, value, success, fail, complete }) {
      let res = { errMsg: 'setStorage:ok' }
      if (typeof key === 'string') {
        localStorage.setItem(key, value);
        success && success(res);
      } else {
        fail && fail(res);
        return Promise.reject(res);
      }
      complete && complete(res);
      return Promise.resolve({ errMsg: 'setStorage:ok' });
    }...
    

    把这个 API 实现挂载到 Taro 模块之后,我们就可以通过 Taro.setStorage 来调用这个 API 了。

    当然,也有一些 API 是 Web 端无论如何无法实现的,比如 wx.login,又或者 wx.scanCode。我们维护了一个 API 实现情况的列表,在实际的多端项目开发中应该尽可能避免使用它们…

    8.3 路由

    小程序的路由比较轻量。使用时,我们先通过 app.json 为小程序配置页面列表:

    {
      "pages": [
        "pages/index/index",
        "pages/logs/logs"
      ],
      // ...
    }
    

    对于 Web 端单页应用路由,我们则以 react-router 为例进行说明

    • 首先,react-router 开始通过 history 工具监听页面路径的变化。
    • 在页面路径发生变化时,react-router 会根据新的 location 对象,触发 UI 层的更新。
    • 至于 UI 层如何更新,则是取决于我们在 Route 组件中对页面路径和组件的绑定,甚至可以实现嵌套路由。
    • 可以说,react-router 的路由方案是组件级别的。
    • 具体到 Taro,为了保持跟小程序的行为一致,我们不需要细致到组件级别的路由方案,但需要为每次路由保存完整的页面栈。
    • 实现形式上,我们参考 react-router:监听页面路径变化,再触发 UI 更新。这是 React 的精髓之一,单向数据流…

    Taro(三)

    /* 示例代码 */
    window.addEventListener("hashchange", () => {});
    window.addEventListener("popstate", () => {});
    
    • 对于使用 Hash 模式的页面路由,每次页面跳转都会依次触发 popstate 和 hashchange 事件。由于在 popstate 的回调中可以取到当前页面的 state,我们选择它作为主要跳转逻辑的容器。
    • 作为 UI 层,@tarojs/router 包提供了一个 Router 组件,维护页面栈。与小程序类似,用户不需要手动调用 Router 组件,而是由 Taro 自动处理。
    • 对于历史栈来说,无非就是三种操作:push, pop,还有 replace。在历史栈变动时触发 Router 的回调,就可以让 Router 也同步变化。这就是 Taro 中路由的基本原理…

    8.4 Redux 处理

    • 每当提到 React 的数据流,我们就不得不提到 Redux。通过合并 Reducer,Redux 可以让大型应用中的数据流更加规则、可预测。
    • 我们在 Taro 中加入了 Redux 的支持,通过导入@tarojs/redux,即可在小程序端使用 Redux 的功能。
    • 对于 Web 端,我们尝试直接使用 nerv-redux 包提供支持,但这会带来一些问题…
    import Nerv from 'nervjs'
    import { connect } from 'nerv-redux'
    
    @connect(() => {})
    class Index extends Nerv.Componnet {
      componentDidShow() { console.log('didShow') }
      componentDidMount() { console.log('didMount') }
      render() { return '' }
    }...
    
    • 回想一下前面讲的 componentDidShow 的实现:我们继承,并且改写 componentDidMount。
    • 但是对于使用 Redux 的页面来说,我们继承的类,是经过@connect 修饰过的一个高阶组件。
    • 问题就出在这里:这个高阶组件的签名里并没有 componentDidShow 这一个函数。所以我们的 componentDidMount 内,理所当然是取不到 componentDidShow 的。
    • 为了解决这个问题,我们对 react-redux 代码进行了一些小改装,这就是@taro/redux-h5 的由来…

    九、更多参考

    Taro 官方文档 taro.aotu.io/home/in.htm…

    十、官方文档

    Taro 小册子 git.jd.com/huangli47/t…

    原文:https://yolkpie.net/2020/10/17/Taro(%E4%B8%89)/


    起源地下载网 » Taro(三)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元