成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于 JSX 的動態(tài)數(shù)據(jù)綁定

Sleepy / 2332人閱讀

摘要:基于的動態(tài)數(shù)據(jù)綁定歸屬于筆者的與前端工程化實踐中的,本文中設(shè)計的引用資料參考學習與實踐資料索引,如果有對基礎(chǔ)語法尚存疑惑的可以參閱現(xiàn)代開發(fā)語法基礎(chǔ)與實踐技巧。

基于 JSX 的動態(tài)數(shù)據(jù)綁定歸屬于筆者的 React 與前端工程化實踐中的,本文中設(shè)計的引用資料參考 React 學習與實踐資料索引,如果有對 JavaScript 基礎(chǔ)語法尚存疑惑的可以參閱現(xiàn)代 JavaScript 開發(fā):語法基礎(chǔ)與實踐技巧。

基于 JSX 的動態(tài)數(shù)據(jù)綁定

筆者在 2016-我的前端之路: 工具化與工程化一文中提及,前端社區(qū)用了 15 年的時間來分割 HTML、JavaScript 與 CSS,但是隨著 JSX 的出現(xiàn)仿佛事物一夕回到解放前。在 Angular、Vue.js 等 MVVM 前端框架中都是采用了指令的方式來描述業(yè)務邏輯,而 JSX 本質(zhì)上還是 JavaScript,即用 JavaScript 來描述業(yè)務邏輯。雖然 JSX 被有些開發(fā)者評論為丑陋的語法,但是筆者還是秉持 JavaScript First 原則,盡可能地用 JavaScript 去編寫業(yè)務代碼。在前文 React 初窺:JSX 詳解中我們探討了 JSX 的前世今生與基本用法,而本部分我們著手編寫簡單的面向 DOM 的 JSX 解析與動態(tài)數(shù)據(jù)綁定庫;本部分所涉及的代碼歸納于 Ueact 庫。

JSX 解析與 DOM 元素構(gòu)建 元素構(gòu)建

筆者在 JavaScript 語法樹與代碼轉(zhuǎn)化實踐 一文中介紹過 Babel 的原理與用法,這里我們?nèi)匀皇褂?Babel 作為 JSX 語法解析工具;為了將 JSX 聲明轉(zhuǎn)化為 createElement 調(diào)用,這里需要在項目的 .babelrc 文件中做如下配置:

  "plugins": [
    "transform-decorators-legacy",
    "async-to-promises",
    [
      "transform-react-jsx", {
        "pragma": "createElement"
      }
    ]
  ],

這里的 createElement 函數(shù)聲明如下:

/**
 * Description 從 JSX 中構(gòu)建虛擬 DOM
 * @param tagName 標簽名
 * @param props 屬性
 * @param childrenArgs 子元素列表
 */
export function createElement(
  tagName: string,
  props: propsType,
  ...childrenArgs: [any]
) {}

該函數(shù)包含三個參數(shù),分別指定標簽名、屬性對象與子元素列表;實際上經(jīng)過 Babel 的轉(zhuǎn)化之后,JSX 文本會成為如下的函數(shù)調(diào)用(這里還包含了 ES2015 其他的語法轉(zhuǎn)化):

...
  (0, _createElement.createElement)(
    "section",
    null,
    (0, _createElement.createElement)(
      "section",
      null,
      (0, _createElement.createElement)(
        "button",
        { className: "link", onClick: handleClick },
        "Custom DOM JSX"
      ),
      (0, _createElement.createElement)("input", {
        type: "text",
        onChange: function onChange(e) {
          console.log(e);
        }
      })
    )
  ),
...

在獲取到元素標簽之后,我們首先要做的就是創(chuàng)建元素;創(chuàng)建元素 createElementByTag 過程中我們需要注意區(qū)分普通元素與 SVG 元素:

export const createElementByTag = (tagName: string) => {
  if (isSVG(tagName)) {
    return document.createElementNS("http://www.w3.org/2000/svg", tagName);
  }
  return document.createElement(tagName);
};
屬性處理

在創(chuàng)建了新的元素對象之后,我們需要對 createElement 函數(shù)傳入的后續(xù)參數(shù)進行處理,也就是為元素設(shè)置對應的屬性;基本的屬性包含了樣式類、行內(nèi)樣式、標簽屬性、事件、子元素以及樸素的 HTML 代碼等。首先我們需要對子元素進行處理:

// 處理所有子元素,如果子元素為單純的字符串,則直接創(chuàng)建文本節(jié)點
const children = flatten(childrenArgs).map(child => {
  // 如果子元素同樣為 Element,則創(chuàng)建該子元素的副本
  if (child instanceof HTMLElement) {
    return child;
  }

  if (typeof child === "boolean" || child === null) {
    child = "";
  }

  return document.createTextNode(child);
});

這里可以看出,對 createElement 函數(shù)的執(zhí)行是自底向上執(zhí)行的,因此傳入的子元素參數(shù)實際上是已經(jīng)經(jīng)過渲染的 HTML 元素。接下來我們還需要對其他屬性進行處理:

...
// 同時支持 class 與 className 設(shè)置
const className = props.class || props.className;

// 如果存在樣式類,則設(shè)置
if (className) {
  setAttribute(tagName, el, "class", classNames(className));
}

// 解析行內(nèi)樣式
getStyleProps(props).forEach(prop => {
  el.style.setProperty(prop.name, prop.value);
});

// 解析其他 HTML 屬性
getHTMLProps(props).forEach(prop => {
  setAttribute(tagName, el, prop.name, prop.value);
});

// 設(shè)置事件監(jiān)聽,這里為了解決部分瀏覽器中異步問題因此采用同步寫法
let events = getEventListeners(props);

for (let event of events) {
  el[event.name] = event.listener;
}
...

React 中還允許直接設(shè)置元素的內(nèi)部 HTML 代碼,這里我們也需要判斷是否存在有 dangerouslySetInnerHTML 屬性:

// 如果是手動設(shè)置 HTML,則添加 HTML,否則設(shè)置顯示子元素
if (setHTML && setHTML.__html) {
  el.innerHTML = setHTML.__html;
} else {
  children.forEach(child => {
    el.appendChild(child);
  });
}

到這里我們就完成了針對 JSX 格式的樸素的 DOM 標簽轉(zhuǎn)化的 createElement 函數(shù),完整的源代碼參考這里。

簡單使用

這里我們依舊使用 create-webpack-app 腳手架來搭建示例項目,這里我們以簡單的計數(shù)器為例描述其用法。需要注意的是,本部分尚未引入雙向數(shù)據(jù)綁定,或者說是自動狀態(tài)變化更新,還是使用的樸素的 DOM 選擇器查詢更新方式:

// App.js
import { createElement } from "../../../src/dom/jsx/createElement";

// 頁面內(nèi)狀態(tài)
const state = {
  count: 0
};

/**
 * Description 點擊事件處理
 * @param e
 */
const handleClick = e => {
  state.count++;
  document.querySelector("#count").innerText = state.count;
};

export default (
  
{ console.log(e); }} />

{state.count}
); // client.js // @flow import App from "./component/Count"; document.querySelector("#root").appendChild(App);
數(shù)據(jù)綁定

當我們使用 Webpack 在后端編譯 JSX 時,會將其直接轉(zhuǎn)化為 JavaScript 中函數(shù)調(diào)用,因此可以自然地在作用域中聲明變量然后在 JSX 中直接引用;不過筆者在設(shè)計 Ueact 時考慮到,為了方便快速上手或者簡單的 H5 頁面開發(fā)或者已有的代碼庫的升級,還是需要支持運行時動態(tài)編譯的方式;本部分我們即討論如何編寫 JSX 格式的 HTML 模板并且進行數(shù)據(jù)動態(tài)綁定。本部分我們的 HTML 模板即是上文使用的 JSX 代碼,不同的是我們還需要引入 babel-standalone 以及 Ueact 的 umd 模式庫:

然后在本頁面的 script 標簽中,我們可以對模板進行渲染并且綁定數(shù)據(jù):

這里我們調(diào)用 Ueact.observeDOM 函數(shù)對模板進行渲染,該函數(shù)會獲取指定元素的 outerHTML 屬性,然后通過 Babel 動態(tài)插件進行編譯:

    let input = html2JSX(ele.outerHTML);

    let output = Babel.transform(input, {
      presets: ["es2015"],
      plugins: [
        [
          "transform-react-jsx",
          {
            pragma: "Ueact.createElement"
          }
        ]
      ]
    }).code;

值得一提的是,因為 HTML 語法與 JSX 語法存在一定的差異,我們獲取渲染之后的 DOM 對象之后,還需要對部分元素語法進行修正;主要包括了以下三個場景:

自閉合標簽處理,即 =>

去除輸入的 HTML 中的事件監(jiān)聽的引號,即 onclick="{methods.handleClick}" => onclick={methods.handleClick}

移除 value 值額外的引號,即 value="{state.a}" => value={state.a}

到這里我們得到了經(jīng)過 Babel 轉(zhuǎn)化的函數(shù)調(diào)用代碼,下面我們就需要去執(zhí)行這部分代碼并且完成數(shù)據(jù)填充。最簡單的方式就是使用 eval 函數(shù),不過因為該函數(shù)直接暴露在了全局作用域下,因此并不被建議使用;我們使用動態(tài)構(gòu)造 Function 的方式來進行調(diào)用:

/**
 * Description 從輸入的 JSX 函數(shù)字符串中完成構(gòu)建
 * @param innerContext
 */
function renderFromStr(innerContext) {
  let func = new Function(
    "innerContext",
    `
     let { state, methods, hooks } = innerContext;
     let ele = ${innerContext.rawJSX}
     return ele;
    `
  ).bind(innerContext);

  // 構(gòu)建新節(jié)點
  let newEle: Element = func(innerContext);

  // 使用指定元素的父節(jié)點替換自身
  innerContext.root.parentNode.replaceChild(newEle, innerContext.root);

  // 替換完畢之后刪除舊節(jié)點的引用,觸發(fā) GC
  innerContext.root = newEle;
}

innerContext 即包含了我們定義的 State 與 Methods 等對象,這里利用 JavaScript 詞法作用域(Lexical Scope)的特性進行變量傳遞;本部分完整的代碼參考這里。

變化監(jiān)聽與重渲染

筆者在 2015-我的前端之路:數(shù)據(jù)流驅(qū)動的界面中討論了從以 DOM 為核心到數(shù)據(jù)流驅(qū)動的變化,本部分我們即討論如何自動監(jiān)聽狀態(tài)變化并且完成重渲染。這里我們采用監(jiān)聽 JavaScript 對象屬性的方式進行狀態(tài)變化監(jiān)聽,采用了筆者另一個庫 Observer-X,其基本用發(fā)如下:

import { observe } from "../../dist/observer-x";

const obj = observe(
  {},
  {
    recursive: true
  }
);

obj.property = {};

obj.property.listen(changes => {
  console.log(changes);
  console.log("changes in obj");
});

obj.property.name = 1;

obj.property.arr = [];

obj.property.arr.listen(changes => {
  // console.log("changes in obj.arr");
});

// changes in the single event loop will be print out

setTimeout(() => {
  obj.property.arr.push(1);

  obj.property.arr.push(2);

  obj.property.arr.splice(0, 0, 3);
}, 500);

核心即是當某個對象的屬性發(fā)生變化(增刪賦值)時,觸發(fā)注冊的回調(diào)事件;即:

  ...
  // 將內(nèi)部狀態(tài)轉(zhuǎn)化為可觀測變量
  let state = observe(innerContext.state);
  ...
  state.listen(changes => {
    renderFromStr(innerContext);
    innerContext.hooks.updated && innerContext.hooks.updated();
  });
  ...

完整的在線 Demo 可以查看 基于 JSX 與 Observer-X 的簡單計數(shù)器

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84656.html

相關(guān)文章

  • React-JSX實現(xiàn)Class與 Style 動態(tài)綁定

    摘要:錯誤例子可以實現(xiàn)的語法如下使用邏輯運算符文件文件使用三元運算符文件使用函數(shù)綁定的數(shù)據(jù)對象也不必內(nèi)聯(lián)定義在模板里可以定義一個函數(shù),類似中的鉤子函數(shù)。 作者:羽徵 摘要:操作元素的 class 列表和內(nèi)聯(lián)樣式是數(shù)據(jù)綁定的一個常見需求,頻繁操作dom元素會降低javascript性能,為了實現(xiàn)高性能js,動態(tài)綁定class和style是高素養(yǎng)程序員的必選。本文以React-JSX語法為基礎(chǔ),...

    phodal 評論0 收藏0
  • React-JSX實現(xiàn)Class與 Style 動態(tài)綁定

    摘要:錯誤例子可以實現(xiàn)的語法如下使用邏輯運算符文件文件使用三元運算符文件使用函數(shù)綁定的數(shù)據(jù)對象也不必內(nèi)聯(lián)定義在模板里可以定義一個函數(shù),類似中的鉤子函數(shù)。 作者:羽徵 摘要:操作元素的 class 列表和內(nèi)聯(lián)樣式是數(shù)據(jù)綁定的一個常見需求,頻繁操作dom元素會降低javascript性能,為了實現(xiàn)高性能js,動態(tài)綁定class和style是高素養(yǎng)程序員的必選。本文以React-JSX語法為基礎(chǔ),...

    waterc 評論0 收藏0
  • React一——起源、安裝、jsx

    摘要:起源官網(wǎng)公司出品,因為對市場上所有框架,都不滿意,就決定自己寫一套,用來架設(shè)的網(wǎng)站。而來自的框架正是完全面向此問題的一個解決方案,按官網(wǎng)描述,其出發(fā)點為用于開發(fā)數(shù)據(jù)不斷變化的大型應用程序。 React 起源 官網(wǎng):https://doc.react-china.org/d...Facebook公司出品,因為對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用...

    reclay 評論0 收藏0
  • 手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認可。監(jiān)聽事件手動維護列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    Channe 評論0 收藏0
  • 手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認可。監(jiān)聽事件手動維護列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    zgbgx 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<