欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

用 Vue 3.0 來寫個小程序框架 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網站建設-昆明葵宇信息科技有限公司

159-8711-8523

云南網建設/小程序開發(fā)/軟件開發(fā)

知識

不管是網站,軟件還是小程序,都要直接或間接能為您產生價值,我們在追求其視覺表現(xiàn)的同時,更側重于功能的便捷,營銷的便利,運營的高效,讓網站成為營銷工具,讓軟件能切實提升企業(yè)內部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

用 Vue 3.0 來寫個小程序框架

發(fā)表時間:2021-1-5

發(fā)布人:葵宇科技

瀏覽次數(shù):53

由于小程序的開發(fā)起來比較原始復雜且繁瑣,跟我們主流的開發(fā)方式差距很大,所以為了提高我們開發(fā)小程序的效率,市面上出現(xiàn)過很多的小程序的框架:mpvue,Taro,uni-app 等等,這些框架或多或少地將我們帶到現(xiàn)代化的開發(fā)方式中來,他們可以讓你使用 React 或者 Vue 來開發(fā)小程序。今天就分享一個如何利用 Vue 3.0 來構建一個小程序的框架。


基礎知識

Vue 3.0

簡單看看 Vue 3.0 有哪些新特性:

Composition-API

Composition-API 是一套讓你可以很方便抽取邏輯函數(shù)的 API,相比于之前的 Options API,其代碼組織能力更強,相同的邏輯可以寫在同一個地方,各個邏輯之間界限分明。

看下面的例子即可說明:

Fragment, Teleport

有點類似于 React 的 Fragment,使得我們在寫 Vue 的模板時不再限制于需要一個根節(jié)點,在 Vue3.0 里可以有多個根節(jié)點。

Teleport 用一種直接聲明的方式來將子組件安裝到 DOM 中的其他位置,類似于 React 的 Portal,但是功能更加強大。

更好的 TypeScript 支持

現(xiàn)在 Vue 3.0 的代碼都是由 TS 來編寫,加上 Composition-Api,再寫業(yè)務代碼的時候可以無縫切換到 TS 。

Custom Render API

利用這套 API 可以很方便的構建出自定義的渲染層,這個也是我們接下來需要重點講的。

import {
  createRenderer,
  CreateAppFunction,
} from '@vue/runtime-core';
export const { render, createApp: baseCreateApp } = createRenderer({
  patchProp, // 修改 props 的函數(shù)
  ...nodeOps, // 修改 dom 節(jié)點的函數(shù)
});
render();
復制代碼

小程序

要開發(fā)一個小程序的頁面基本上我們只需要四個文件:

index.js

index.js 就是我們寫代碼邏輯的地方。

  1. 有一個 Page 函數(shù),里面是對象配置,類似于 Vue 的 options 配置一樣,有一個 data 屬性,存放著初始化的數(shù)據(jù)。
  2. 如果想要修改數(shù)據(jù)改變視圖,又需要像  react 一樣,需要調用 setData 去修改視圖。
Page({
     data: {
       text: 'hello word'
    },
    onLoad() {
        this.setData({
            text: 'xxxxx'
        })
    },
    onReady() {},
    onShow() {},
    onHide() {},
    onUnload() {},
    handleClick() {
        this.setData({
            text: 'hello word'
        })
    }
})
復制代碼

index.ttml

index.ttml 是我們寫視圖模板的地方。

  1. 類似于 vue 的 template,我們需要先定義模板才能顯示視圖
  2. 注意: 不能直接在 index.js 里面去修改定義的模板的 DOM,只能先定義好,這是由于小程序架構雙線程導致的,分為邏輯層和渲染層,我們寫的 index.js 代碼跑在邏輯層里面,index.ttml 跑在渲染層里面,兩個線程就通過 setData 進行數(shù)據(jù)交換。

index.json

配置小程序頁面和組件的地方,暫時不列出參數(shù),但是一定要有這個文件。

index.ttss

顧名思義,就是寫樣式的地方,類似于 CSS。

模板

小程序為了封裝的方便,可以先提前定義一個模板,然后再需要的地方引入模板即可,有點像 ejs 和 pug 的 import template 的用法

動態(tài)模板

上面說到,小程序里面不能動態(tài)的修改 DOM 節(jié)點,只能提前定義好 template,然后通過 setData 的形式去修改視圖。

但是小程序又有個比較動態(tài)的特性,叫做動態(tài)選擇模板。

// 使用這個模板
 <template is="{{type}}" data="http://www.wxapp-union.com/{{item: item}}"/>
復制代碼

上面 is 屬性的 type 就是動態(tài)的,它是個變量,可以根據(jù) type 的值來選擇不同的模板,比如 type 為 view 時,就會渲染我們提前定義好的 view template。

自定義渲染層(非常重要)

重頭戲來了,我們該如何利用 Vue 3.0 方便的自定義渲染層 結合 小程序的動態(tài)選擇模板的特性來去寫一個小程序的框架呢?

import {
  createRenderer,
  CreateAppFunction,
} from '@vue/runtime-core';
export const { render, createApp: baseCreateApp } = createRenderer({
  patchProp, // 修改 props 的函數(shù)
  ...nodeOps, // 修改 dom 節(jié)點的函數(shù)
});
復制代碼

我們可以看到 `createRenderer`函數(shù)需要兩個參數(shù),一個是 patchProp,一個是 nodeOps。

nodeOps

nodeOps 代表著修改 node 節(jié)點的一些操作,從而可以去改變視圖,比如在 Vue 3.0 的瀏覽器環(huán)境中,是這么寫的:

import { RendererOptions } from '@vue/runtime-core'
export const svgNS = 'http://www.w3.org/2000/svg'
const doc = (typeof document !== 'undefined' ? document : null) as Document
let tempContainer: HTMLElement
let tempSVGContainer: SVGElement
// 瀏覽器環(huán)境下的 nodeOps
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },
  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },
  createElement: (tag, isSVG, is): Element =>
    isSVG
      ? doc.createElementNS(svgNS, tag)
      : doc.createElement(tag, is ? { is } : undefined),
  createText: text => doc.createTextNode(text),
  createComment: text => doc.createComment(text),
  setText: (node, text) => {
    node.nodeValue = http://www.wxapp-union.com/text
  },
  setElementText: (el, text) => {
    el.textContent = text
  },
  parentNode: node => node.parentNode as Element | null,
  nextSibling: node => node.nextSibling,
  querySelector: selector => doc.querySelector(selector),
  setScopeId(el, id) {
    el.setAttribute(id,'')
  },
  cloneNode(el) {
    return el.cloneNode(true)
  },
}
復制代碼

實際上 Vue 不管數(shù)據(jù)怎么變化,要將數(shù)據(jù)顯示到視圖上都是調用了 DOM 的一些 API,像上面的 doc.createElement 和 doc.createTextNode 等等。

VNode

是由于小程序的限制,我們不能直接像瀏覽器環(huán)境一樣去修改 DOM,那我們可以先模仿瀏覽器的環(huán)境,創(chuàng)造出一個虛擬的 DOM,我們叫做 VNode。

class VNode {
  id: number;
  type: string;
  props?: Record<string, any>;
  text?: string;
  children: VNode[] = [];
  eventListeners?: Record<string, Function | Function[]> | null;
  parentNode?: VNode | null;
  nextSibling?: VNode | null;
  constructor({
    id,
    type,
    props = {},
    text,
  }: {
    id: number;
    type: string;
    props?: Record<string, any>;
    text?: string;
  }) {
    this.type = type;
    this.props = props;
    this.text = text;
    this.id = id;
  }
  appendChild(newNode: VNode) {
    if (this.children.find((child) => child.id === newNode.id)) {
      this.removeChild(newNode);
    }
    newNode.parentNode = this;
    this.children.push(newNode);
    setState({ node: newNode, data: newNode.toJSON() }); // 調用了小程序的 setData
  }
  insertBefore(newNode: VNode, anchor: VNode) {
    newNode.parentNode = this;
    newNode.nextSibling = anchor;
    if (this.children.find((child) => child.id === newNode.id)) {
      this.removeChild(newNode);
    }
    const anchorIndex = this.children.indexOf(anchor);
    this.children.splice(anchorIndex, 0, newNode);
    setState({
      node: this,
      key: '.children',
      data: this.children.map((c) => c.toJSON()),
    }); // 調用了小程序的 setData
  }
  removeChild(child: VNode) {
    const index = this.children.findIndex((node) => node.id === child.id);
    if (index < 0) {
      return;
    }
    if (index === 0) {
      this.children = [];
    } else {
      this.children[index - 1].nextSibling = this.children[index + 1];
      this.children.splice(index, 1);
    }
    setState({
      node: this,
      key: '.children',
      data: this.children.map((c) => c.toJSON()),
    });
  }
  setText(text: string) {
    if (this.type === TYPE.RAWTEXT) {
      this.text = text;
      setState({ node: this, key: '.text', data: text });
      return;
    }
    if (!this.children.length) {
      this.appendChild(
        new VNode({
          type: TYPE.RAWTEXT,
          id: generate(),
          text,
        })
      );
      return;
    }
    this.children[0].text = text;
    setState({ node: this, key: '.children[0].text', data: text });
  }
  path(): string {
    if (!this.parentNode) {
      return 'root';
    }
    const path = this.parentNode.path();
    return [
      ...(path === 'root' ? ['root'] : path),
      '.children[',
      this.parentNode.children.indexOf(this) + ']',
    ].join('');
  }
  toJSON(): RawNode {
    if (this.type === TYPE.RAWTEXT) {
      return {
        type: this.type,
        text: this.text,
      };
    }
    return {
      id: this.id,
      type: this.type,
      props: this.props,
      children: this.children && this.children.map((c) => c.toJSON()),
      text: this.text,
    };
  }
}
復制代碼

可以看到我們創(chuàng)建的 VNode 類似于 DOM,也有一些操作 Node 節(jié)點的方法,最終生成一個 Node 樹。我們就可以仿照 vue 瀏覽器環(huán)境的 nodeOps 寫法,先去修改我們的 VNode,在修改 Node 節(jié)點的同時里面我們可以去調用小程序的 setData 方法。

// 小程序環(huán)境下的 nodeOps,主要是修改 VNode
export const nodeOps = {
  insert: (child: VNode, parent: VNode, anchor?: VNode) => {
    if (anchor != null) {
      parent.insertBefore(child, anchor);
    } else {
      parent.appendChild(child);
    }
  },
  remove: (child: VNode) => {
    const parent = child.parentNode;
    if (parent != null) {
      parent.removeChild(child);
    }
  },
  createElement: (tag: string): VNode =>
    new VNode({ type: tag, id: generate() }),
  createText: (text: string): VNode =>
    new VNode({ type: TYPE.RAWTEXT, text, id: generate() }),
  createComment: (): VNode => new VNode({ type: TYPE.RAWTEXT, id: generate() }),
  setText: (node: VNode, text: string) => {
    node.setText(text);
  },
  setElementText: (el: VNode, text: string) => {
    el.setText(text);
  },
  parentNode: (node: VNode): VNode | null => node.parentNode ?? null,
  nextSibling: (node: VNode): VNode | null => node.nextSibling ?? null,
  querySelector: (): VNode | null => getApp()._root,
  setScopeId(el: VNode, id: string) {
    if (el.props) {
      const className = el.props.class;
      el.props.class = className ? className + ' ' + id : id;
    }
  },
};
復制代碼

toJSON()

光是創(chuàng)造出 VNode 還不夠,我們得讓它渲染到小程序里面去,小程序要先渲染出數(shù)據(jù)必須是提前在 data 屬性里面定義的數(shù)據(jù),而且只能是普通的數(shù)據(jù)類型。

Page({
    data: {
        root: {
            type: 'view',
            props: {
                class: 'xxx'
            },
            children: [...]    
        }    
    }
})
復制代碼

toJSON 方法就是可以將一個 VNode 給格式化成普通的對象,讓小程序可以渲染出數(shù)據(jù)。

接口類型如下:

interface RawNode {
  id?: number;
  type: string; // view,input, button
  props?: Record<string, any>;
  children?: RawNode[];
  text?: string; // 文本
}
復制代碼

是不是跟 VDOM 的結構很熟悉?

path()

我們可以看到在我們定義的 VNode 里面,里面有個 path() 方法,這個方法就是獲取 Node 節(jié)點在整個節(jié)點樹的一個路徑,然后可以利用 path 去修改某一個特定的 Node 節(jié)點。

const path = Node.path(); // root.children[2].props.class
// 然后我們可以直接這樣來更新小程序
this.setData({
  'root.children[2].props.class': 'xxxxx'
})
復制代碼

結合動態(tài)選擇模板

<template name="$_TPL">
  <block tt:for="{{root.children}}" tt:key="{{id}}">
    <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item: item}}"/>
  </block>
</template>
<template name="$_input">
  // input 有三個屬性 class 和 bindinput 和 value 對應 vue 文件 template 里的 input 上的屬性 class @input value
  <input class="{{item.props['class']}}" bindinput="{{item.props['bindinput']}}" value="http://www.wxapp-union.com/{{item.props['value']}}">
    <block tt:for="{{item.children}}" tt:key="{{id}}">
      <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item}}"/>
    </block>
  </input>
</template>
<template name="$_button">
   // button 有兩個屬性 class 和 bindTap 對應 vue 文件 template 里的 button 上的屬性
  <button class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
    <block tt:for="{{item.children}}" tt:key="{{id}}">
      <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item}}"/>
    </block>
  </button>
</template>
<template name="$_view">
  <view class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
    <block tt:for="{{item.children}}" tt:key="{{id}}">
      <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item}}"/>
    </block>
  </view>
</template>
<template name="$_rawText">{{item.text}}</template>
復制代碼

編譯層

我們寫的代碼肯定是 Vue 的代碼,不是上面的模板代碼,那么 Vue 的代碼改怎么樣去編譯到上面的模板代碼呢?

先看一下整體架構圖:

Template 

如果我們寫的業(yè)務代碼是常見的 vue 指令模板模式,那么我們可以在底層使用 @vue/compile-core 來 parse Vue 的 template,然后遍歷 parse 后的 AST,收集其中用到的 tag 和 props。

import { parse } from '@vue/compiler-sfc';
import {
  baseCompile,
} from '@vue/compiler-core';
const { descriptor } = parse(source, {
    filename: this.resourcePath,
  });
// 遍歷這個 ast 去收集 tag 和 props
const { ast } = baseCompile(descriptor.template.content);
復制代碼

JSX/TSX

如果我們寫的業(yè)務代碼是 JSX/TSX,那么這邊可以寫個收集 Tag 和 props 的 babel plugin,在 babel plugin 里面去遍歷 AST,收集 Tag 和 props。

最終生成的 ttml

假如我們有一個 .vue 文件:

<template>
  <div class="container is-fluid">
    <div class="subtitle is-3">Add todo list</div>
    <div class="field">
      <div class="control">
        <input class="input is-info" @input="handleInput" :value="http://www.wxapp-union.com/todo" />
      </div>
    </div>
    <button class="button is-primary is-light" @click="handleAdd">Add +</button>
  </div>
</template>
<script>
import { ref } from 'vue';
import { useMainStore } from '@/store';
export default {
  setup() {
    const todo = ref('');
    const store = useMainStore();
    const handleInput = (e) => {
      todo.value = http://www.wxapp-union.com/e.detail.value;
    };
    const handleAdd = () => {
      store.addTodo(todo.value);
    };
    return {
      handleInput,
      todo,
      handleAdd,
    };
  },
};


復制代碼

會生成下面的模板:

<template name="$_TPL">
  <block tt:for="{{root.children}}" tt:key="{{id}}">
    <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item: item}}"/>
  </block>
</template>
<template name="$_input">
  // input 有三個屬性 class 和 bindinput 和 value 對應 vue 文件 template 里的 input 上的屬性 class @input value
  <input class="{{item.props['class']}}" bindinput="{{item.props['bindinput']}}" value="http://www.wxapp-union.com/{{item.props['value']}}">
    <block tt:for="{{item.children}}" tt:key="{{id}}">
      <template is="{{'$_' + item.type}}" data="http://www.wxapp-union.com/{{item}}"/>
    </block>

作者:字節(jié)前端
來源:掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。

相關案例查看更多