亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

前端可視化搭建定義聯(lián)動(dòng)協(xié)議實(shí)現(xiàn)

 更新時(shí)間:2023年05月08日 15:48:16   作者:黃子毅  
這篇文章主要為大家介紹了前端可視化搭建定義聯(lián)動(dòng)協(xié)議實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

雖然底層框架提供了通用的組件值與聯(lián)動(dòng)配置,可以建立對(duì)組件任意 props 的映射,但這只是一個(gè)能力,還不是協(xié)議。

業(yè)務(wù)層是可以確定一個(gè)協(xié)議的,還要讓這個(gè)協(xié)議具有拓展性。

我們先從使用者角度設(shè)計(jì) API,再看看如何根據(jù)已有的組件值與聯(lián)動(dòng)能力去實(shí)現(xiàn)。

設(shè)計(jì)聯(lián)動(dòng)協(xié)議

首先,不同的業(yè)務(wù)方會(huì)定義不同的聯(lián)動(dòng)協(xié)議,因此該聯(lián)動(dòng)協(xié)議需要通過(guò)拓展的方式注入:

import { createDesigner } from 'designer'
import { onReadComponentMeta } from 'linkage-protocol'
return <Designer onReadComponentMeta={onReadComponentMeta} />

首先可視化搭建框架支持 onReadComponentMeta 屬性,用于拓展所有已注冊(cè)的組件元信息,而聯(lián)動(dòng)協(xié)議的拓展就是基于組件值與組件聯(lián)動(dòng)能力的,因此這種是最合理的拓展方式。

之后我們就注冊(cè)了一個(gè)固定的聯(lián)動(dòng)協(xié)議,它形如下:

{
  "componentName": "input",
  "linkage": [{
    "target": "input1",
    "do": {
      "value": "{{ $self.value + 'hello' }}"
    }
  }]
}

只要在組件實(shí)例上定義 linkage 屬性,就可以生效聯(lián)動(dòng)。比如上面的例子:

  • target: 聯(lián)動(dòng)目標(biāo)。
  • do: 聯(lián)動(dòng)效果,比如該例子為,組件 ID 為 input1 的組件,組件值同步為當(dāng)前組件實(shí)例的組件值 + 'hello'。
  • $self: 描述自己實(shí)例,比如可以從 $self.value 拿到自己的組件值,從 $self.props 拿到自己的 props。

更近一步,target 還可以支持?jǐn)?shù)組,就表示同時(shí)對(duì)多個(gè)組件生效相同規(guī)則。

我們還可以支持更復(fù)雜的語(yǔ)法,比如讓該組件可以同步其他組件值:

{
  "componentName": "input",
  "linkage": [{
    "deps": ["input1", "input2"]
    "props": {
      "text": "{{ $deps[0].value + deps[1].value }}"
    }
  }]
}

上面的例子表示,該組件實(shí)例的 props.text 同步為 input1 + input2 的組件值:

  • deps: 描述依賴(lài)列表,每個(gè)依賴(lài)實(shí)例都可以在表達(dá)式里用 $deps[] 訪問(wèn)到,比如 $deps[0].props 可以訪問(wèn)組件 ID 為 input1 組件的 props。
  • props: 同步組件的 props。

如果定義了 target 則作用于目標(biāo)組件,未定義 target 則作用于自身。但無(wú)論如何,表達(dá)式的 $self 都指向自己實(shí)例。

總結(jié)一下,該聯(lián)動(dòng)協(xié)議允許組件實(shí)例實(shí)現(xiàn)以下效果:

  • 設(shè)定組件值、組件 props 的聯(lián)動(dòng)效果。
  • 可以將自己的組件值同步給組件實(shí)例,也可以將其他組件值同步給自己。

基本上,可以滿足任意組件聯(lián)動(dòng)到任意組件的訴求。而且甚至支持組件間傳遞,比如 A 組件的組件值同步組件 B, B 組件的組件值同步組件 C,那么 A 組件 setValue() 后,組件 B 和 組件 C 的組件值會(huì)同時(shí)更新。

實(shí)現(xiàn)聯(lián)動(dòng)協(xié)議

以上聯(lián)動(dòng)協(xié)議只是一種實(shí)現(xiàn),我們可以基于組件值與組件聯(lián)動(dòng)設(shè)定任意協(xié)議,因此實(shí)現(xiàn)聯(lián)動(dòng)協(xié)議的思維具備通用性,但為了方便,我們以上面說(shuō)的這個(gè)協(xié)議為例子,說(shuō)明如何用可視化搭建框架的基礎(chǔ)功能實(shí)現(xiàn)協(xié)議。

首先解讀組件實(shí)例的 linkage 屬性,將聯(lián)動(dòng)定義轉(zhuǎn)化為組件聯(lián)動(dòng)關(guān)系,因?yàn)槁?lián)動(dòng)協(xié)議本質(zhì)上就是產(chǎn)生了組件聯(lián)動(dòng)。接下來(lái)代碼片段比較長(zhǎng),因此會(huì)盡量使用代碼注釋來(lái)解釋?zhuān)?/p>

const extendMeta = {
  // 定義 valueRelates 關(guān)系,就是我們上一節(jié)提到的定義組件聯(lián)動(dòng)關(guān)系的 key
  valueRelates: ({ componentId, selector }) => {
    // 利用 selector 讀取組件實(shí)例 linkage 屬性
    // 由于 selector 的特性,會(huì)實(shí)時(shí)更新,因此聯(lián)動(dòng)協(xié)議變化后,聯(lián)動(dòng)狀態(tài)也會(huì)實(shí)時(shí)更新
    const linkage = selector(({ componentInstance }) => componentInstance.linkage)
    // 返回聯(lián)動(dòng)數(shù)組,結(jié)構(gòu): [{ sourceComponentId, targetComponentId, payload }]
    return linkage.map(relation => {
        const result = [];
        // 定義此類(lèi)聯(lián)動(dòng)類(lèi)型,就叫做 simpleRelation
        const payload = {
          type: 'simpleRelation',
          do: JSON.parse(
            JSON.stringify(relation.do)
              // 將 $deps[index] 替換為 $deps[componentId]
              .replace(
                /\$deps\[([0-9]+)\]/g,
                (match: string, index: string) =>
                  `$deps['${relation.deps[Number(index)]}']`,
              )
              // 將 $self 替換為 $deps[componentId]
              .replace(/\$self/g, () => `$deps['${componentId}']`),
          ),
        };
        // 經(jīng)過(guò)上面的代碼,表達(dá)式里無(wú)論是 $self. 還是 $deps[0]. 都轉(zhuǎn)化為了
        // $deps[componentId] 這個(gè)具體組件 ID,這樣后面處理流程會(huì)簡(jiǎn)單而統(tǒng)一
        // 讀取 deps,并定義 dep 組件作為 source,target 作為目標(biāo)組件
        // 這是最關(guān)鍵的一步,將 dep -> target 關(guān)系綁定上
        relation.target.forEach((targetComponentId) => {
          if (relation.deps) {
            relation.deps.forEach((depIdPath: string) => {
              result.push({
                sourceComponentId: depIdPath,
                targetComponentId,
              });
            });
          }
          // 定義自己到 target 目標(biāo)組件的聯(lián)動(dòng)關(guān)系
          result.push({
            sourceComponentId: componentId,
            targetComponentId,
            payload,
          });
        });
        return result;
      }).flat()
  }
}

上述代碼利用 valueRelates,將聯(lián)動(dòng)協(xié)議的關(guān)聯(lián)關(guān)系提取出來(lái),轉(zhuǎn)化為值聯(lián)動(dòng)關(guān)系。

接著,我們要實(shí)現(xiàn) props 同步功能,實(shí)現(xiàn)這個(gè)功能自然是利用 runtimeProps 以及 selector.relates,將關(guān)聯(lián)到當(dāng)前組件的組件值,按照聯(lián)動(dòng)協(xié)議的表達(dá)式執(zhí)行,并更新到對(duì)應(yīng) key 上,下面是大致實(shí)現(xiàn)思路:

const extendMeta = {
  runtimeProps: ({ componentId, selector, getProps, getMergedProps }) => {
    // 拿到作用于自己的值關(guān)聯(lián)信息: relates
    const relates = selector(({ relates }) => relates);
    // 記錄最終因?yàn)橹德?lián)動(dòng)而影響的 props
    let relationProps: any = {};
    // 記錄關(guān)聯(lián)到自己的組件此時(shí)組件值
    const $deps = relates?.reduce(
      (result, next) => ({
        ...result,
        [next.componentId]: {
          value: next.value,
        },
      }),
      {},
    );
    // 為了讓每個(gè)依賴(lài)變化都能生效,多對(duì)一每一項(xiàng) do 都帶過(guò)來(lái)了,需要按照 relationIndex 先去重
    relates
      .filter((relate) => relate.payload?.type === 'simpleRelation')
      .forEach((relate) => {
        const expressionArgs = {
          // $deps[].value 指向依賴(lài)的 value
          $deps,
          get,
          getProps: relate.componentId === componentId ? getProps : getMergedProps,
        };
        // 處理 props 聯(lián)動(dòng)
        if (isObject(relate.payload?.do?.props)) {
          Object.keys(relate.payload?.do?.props).forEach((propsKey) => {
            relationProps = set(
              propsKey,
              selector(
                () =>
                  // 這個(gè)函數(shù)是關(guān)鍵,傳入組件 props 與表達(dá)式,返回新的 props 值
                  getExpressionResult(
                    get(propsKey, relate.payload?.do?.props),
                    expressionArgs,
                  ),
                {
                  compare: equals,
                  // 根據(jù)表達(dá)式數(shù)量可能不同,所以不啟用緩存
                  cache: false,
                },
              ),
              relationProps,
            );
          });
        }
      });
    return relationProps
  }
}

其中比較復(fù)雜函數(shù)就是 getExpressionResult,它要解析表達(dá)式并執(zhí)行,原理就是利用代碼沙盒執(zhí)行字符串函數(shù),并利用正則替換變量名以匹配上下文中的變量,大致代碼如下:

// 代碼執(zhí)行沙盒,傳入字符串 js 函數(shù),利用 new Function 執(zhí)行
function sandBox(code: string) {
  // with 是關(guān)鍵,利用 with 定制代碼執(zhí)行的上下文
  const withStr = `with(obj) { 
    $[code]
  }`;
  const fun = new Function('obj', withStr);
  return function (obj: any) {
    return fun(obj);
  };
}
// 獲取沙盒代碼執(zhí)行結(jié)果,可以傳入?yún)?shù)覆蓋沙盒內(nèi)上下文
function getSandBoxReturnValue(code: string, args = {}) {
  try {
    return sandBox(code)(args);
  } catch (error) {
    // eslint-disable-next-line no-console
    console.warn(error);
  }
}
// 如果對(duì)象是字符串則直接返回,是 {{}} 表達(dá)式則執(zhí)行后返回
function getExpressionResult(code: string, args = {}) {
  if (code.startsWith('{{') && code.endsWith('}}')) {
    // {{}} 內(nèi)的表達(dá)式
    let codeContent = code.slice(2, code.length - 2);
    // 將形如 $deps['id'].props.a.b.c
    // 轉(zhuǎn)換為 get('a.b.c', getProps('id'))
    codeContent = codeContent.replace(
      /\$deps\[['"]([a-zA-Z0-9]*)['"]\]\.props\.([a-zA-Z0-9.]*)/g,
      (str: string, componentId: string, propsKeyPath: string) => {
        return `get('${propsKeyPath}', getProps('${componentId}'))`;
      },
    );
    return getSandBoxReturnValue(`return ${codeContent}`, args);
  }
  return code;
}

其中 with 是沙盒執(zhí)行時(shí)替換代碼上下文的關(guān)鍵。

總結(jié)

componentMeta.valueRelatescomponentMeta.runtimeProps 可以靈活的定義組件聯(lián)動(dòng)關(guān)系,與更新組件 props,利用這兩個(gè)聲明式 API,甚至可以實(shí)現(xiàn)組件聯(lián)動(dòng)協(xié)議??偨Y(jié)一下,包含以下幾個(gè)關(guān)鍵點(diǎn):

  • depstarget 利用 valueRelates 轉(zhuǎn)化為組件值關(guān)聯(lián)關(guān)系。
  • 將聯(lián)動(dòng)協(xié)議定義的相對(duì)關(guān)系(比較容易寫(xiě)于容易記)轉(zhuǎn)化為絕對(duì)關(guān)系(利用 componentId 定位),方便框架處理。
  • 利用 with 執(zhí)行表達(dá)式上下文。
  • 利用 runtimeProps + selector 實(shí)現(xiàn)注入組件 props 與響應(yīng)聯(lián)動(dòng)值 relates 變化,從而實(shí)現(xiàn)按需聯(lián)動(dòng)。

討論地址是:精讀《定義聯(lián)動(dòng)協(xié)議》· Issue #471 · dt-fe/weekly

以上就是前端可視化搭建定義聯(lián)動(dòng)協(xié)議實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于前端可視化搭建聯(lián)動(dòng)協(xié)議的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 微信小程序(二十)slider組件詳細(xì)介紹

    微信小程序(二十)slider組件詳細(xì)介紹

    這篇文章主要介紹了 微信小程序(二十)slider組件詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2016-09-09
  • 本地存儲(chǔ)localStorage設(shè)置過(guò)期時(shí)間示例詳解

    本地存儲(chǔ)localStorage設(shè)置過(guò)期時(shí)間示例詳解

    這篇文章主要為大家介紹了本地存儲(chǔ)localStorage設(shè)置過(guò)期時(shí)間示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 關(guān)于JavaScript?中?if包含逗號(hào)表達(dá)式

    關(guān)于JavaScript?中?if包含逗號(hào)表達(dá)式

    這篇文章主要介紹了?關(guān)于JavaScript?中?if包含逗號(hào)表達(dá)式,有時(shí)會(huì)看到JavaScript中if判斷里包含英文逗號(hào)?“,”,這個(gè)是其實(shí)是逗號(hào)表達(dá)式。在if條件里,只有最后一個(gè)表達(dá)式起判斷作用。下面來(lái)看看文章的具體介紹吧
    2021-11-11
  • TypeScript枚舉類(lèi)型

    TypeScript枚舉類(lèi)型

    這篇文章主要介紹了TypeScript枚舉類(lèi)型,所謂的枚舉類(lèi)型就是為一組數(shù)值賦予名字,下面我們來(lái)看看文章是怎么介紹的吧,需要的小伙伴也可以參考一下,希望對(duì)你有所幫助
    2021-12-12
  • 微信小程序的生命周期的詳解

    微信小程序的生命周期的詳解

    這篇文章主要介紹了微信小程序的生命周期的詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • 最新評(píng)論