bindview.js 的使用

灿烈^_^發表於2024-03-31

快速入門

1. 建立第一個應用

由於該庫還不支援 src 引入,接下來的例子我將在 webpack 環境下演示,webpack 模板 已經配置完畢,可直接下載使用

建立一個應用可用透過 new 來建立例項或透過提供的 createApp 方法來建立下面我將分別演示

  1. 透過 new 來建立 App , el 配置項用來選擇 DOM 被渲染到那個節點下, render 方法返回一個虛擬 DOMh 函式可以建立一個虛擬 DOMrender 返回的虛擬 DOM 將被轉換為真實 DOM 並新增到 id 為 Root 的 節點下,!!! 透過 new 建立的例項常常用來配置一些全域性的方法,資料和元件
// 匯入 Bindview.js
import Bindview from "bindview"

// new 一個例項
new Bindview({
    el:"#Root",
    render(h){
        return h("div",{},["hello"]) 
    }
})
  1. 透過 createApp 方法來建立 App , 在 createApp 方法中傳入一個元件,在透過 $mount 方法將虛擬 DOM 新增到 id 為 Root 的 節點下,如何建立一個元件將在後面講到
import { createApp } from "bindview"
import App from "./App"

createApp(App).$mount("#Root")

2. 虛擬 DOM 的建立

  1. 透過 h 函式來建立虛擬 DOM , 虛擬 DOM 本質上就是一個 真實 DOM 抽象為一個 js 物件,物件上記錄了 DOM 的型別,屬性和子節點的資訊,在 render 方法中可以透過 h 函式來建立虛擬 DOM , h 透過下面的方式都可以建立出虛擬 DOM , 透過 h 的的函式來建立 虛擬 DOM 的方式,不是很推薦,因為過程太繁瑣我推薦使用 JSX 的方式來建立
// 匯入 Bindview.js
import Bindview from "bindview"

new Bindview({
    el: '#Root',
    render(h) {
      return h('ul', {}, [
      	h('li', {} , []),
        h('li', null , [])
    ])
    },
  })
  1. 透過 JSX 來建立,在我配置好的 webpack 模板 下可以使用 JSX 來建立虛擬 DOM ,是非常推薦的做法,後面的例子都將使用 JSX 的形式
// 匯入 Bindview.js
import Bindview from "bindview"

new Bindview({
    el: '#Root',
    render() {
      return (
      	<div>Hello World</div>
      )
    },
  })

3. 元件的定義和使用

元件是 Bindview 中的一個重要概念,是一個可以重複使用的 Bindview例項,它擁有獨一無二的元件名稱,它可以擴充套件虛擬 DOM,以元件名稱的方式作為自定義的虛擬 DOM。因為元件是可複用的 Bindview 例項,所以它們與new Bindview()接收相同的選項,例如datarender(h){}methods以及生命週期鉤子等。
把一些公共的模組抽取出來,然後寫成單獨的的工具元件或者頁面,在需要的頁面中就直接引入即可。那麼我們可以將其抽出為一個元件進行復用。

定義一個元件,Bindview 的元件是一個函式返回一個配置對像,物件中只有 render 配置項是必須的其他的配置項都不是必須的, 函式的 props 形參 用來接收一些傳遞給元件的引數,元件中還有一些功能將在後面的內容中講到

// 定義一個 App 元件
export default function App(props) {
  return {
    name: 'App',
    render() {
      return (
        <div id="App">
          <div>Hello World</div>
        </div>
      )
    }
  }
}

使用元件, 將元件註冊到 components 配置項中,!!! 元件名一定要大寫,雖然可以小寫但這是為了避免一些錯誤,然後使用 JSX 方式來書寫就可以使用了

// 匯入 Bindview.js
import Bindview from "bindview"
import App from "./App"

// new 一個例項
new Bindview({
    el:"#Root",
    render:() => (<App />),
    components:{ App }
})

4. data 配置項和資料響應式

Bindview 使用了一種類似於 MVVM 的設計模式,資料和檢視進行繫結,頁面的顯示結果將受 data 配置項中的資料影響,而 data 配置項中的資料使用了 Vue3 資料代理的方法,修改 data 中的資料,檢視將自動更新

在下面這個例子中,data 中配置一個 num 是資料,在 render 方法中 this 可以獲取到整個元件是例項,我們透過 this 解構出 data 並重新命名為 $ ,在 JSX 中使用資料,在 button 上繫結了一個點選事件來對資料進行自增,當點選 頁面上的 button 按鈕時頁面上的資料將自動更新

export default function () {
  return {
    name: 'App',
    render() {
      const { data: $ } = this

      return (
        <div id="App">
          <div>{$.num}</div>
          <button onClick={() => $.num++}>Button</button>
        </div>
      )
    },
    data:()=>({
         num: 0
    })
  }
}

5. methods 配置項

methods 配置項用來定義一些元件內部需要使用的方法, 下面的例子中在 methods 中定義了一個 Add 方法來對 data 中的 num 進行自增,在 onClcik 事件繫結中使用當點選 button 時將呼叫該方法,Add 方法的 this 指向元件例項, !!! 事件繫結中的方法或函式會接收到兩個引數 第一個是事件繫結的 DOM 元素,第二個是事件物件 event , *** 如果元件需要使用一些方法和函式這並不是唯一的方式

export default function () {
  return {
    name: 'App',
    render() {
      const { data: $,methods: f } = this

      return (
        <div id="App">
          <div>{$.num}</div>
          <button onClick={f.Add}>Button</button>
        </div>
      )
    },
    data: {
      num: 0
    },
    methods:{
        Add(){
            this.data.num++
        }
    }
  }
}

6. ref 獲取 DOM

ref 被用來給元素註冊引用資訊。引用資訊將會註冊在父元件的 refs 物件上。在 DOM 元素上使用,引用指向的就是 DOM 元素, ref 可以傳入一個 字串 或一個 函式 ,傳入字串的 DOM 元素將引用到 refs 上,傳入函式函式會接收的 DOM 元素,在元件上使用 ref 只能使用傳入函式,函式接收的元件例項

export default function () {
  let dom // 接收 dom 元素的例項
  return {
    name: 'App',
    render() {
      return (
        <div id="App">
          <div ref="div">hello</div>
          <div ref={_dom => dom = _dom}>world</div>
        </div>
      )
    },
    life:{
        createDom(){
            console.log(this.refs) // { div:HTMLDivElement }
        }
    }
  }
}

如果使用了相同的 ref 資訊那麼,refs 物件中該資訊將是一個陣列儲存了使用相同資訊的 DOM

export default function () {
  return {
    name: 'App',
    render() {
      return (
        <div id="App">
          <div ref="box">hello</div>
          <div ref="box">world</div>
        </div>
      )
    },
    life:{
        createDom(){
            console.log(this.refs) // { box:[ HTMLDivElement , HTMLDivElement ] }
        }
    }
  }
}

7. linkage 聯動更新

Bindview 中當父元件中觸發更新時 ( 一般是 data 中的資料發生改變 ) 父元件會觸發所有後代元件的更新,因為父元件不知道那些後代元件使用了它自身的一些資料,所以他會觸發所有後代元件的更新方法,如果後代元件是檢視模型中發生了變化,那麼元件將更新檢視,但在開發過程中一些元件只做展示效果那麼可以透過 linkage 配置項來關閉父元件對自身的聯動更新,*** 注意 linkage 只會關閉父元件對子元件的聯動更新,而不會關閉子元件自身的資料響應式

下面的例子中給 Son 子元件配置了 linkage 配置項當改變父元件中的 num 的值時,子元件將不會更新來獲取最新的資料來更新檢視

function Son(props) {
  const { num } = props
  return {
    name: 'Son',
    linkage: false,
    render() {
      return (
        <div>{num()}</div>
      )
    }
  }
}

export default function () {
  return {
    name: 'Dome',
    render() {
      const { data: $ } = this
      return (
        <div>
          <button onClick={() => $.num++}>num++</button>
          <Son num={() => $.num} />
        </div>
      )
    },
    data:()=>({
      num: 0
    }),
    components: { Son }
  }
}

8. 插槽

插槽是元件中不確定的部分由使用者來定義,這部分就叫插槽,相當於一種佔位符,在元件中透過元件函式的 slot 來獲取插槽, slot 函式將返回插槽的虛擬 DOM ,在 render 中直接使用即可

Bindview 中元件插槽有兩種一種是 普通插槽 還有一種是 函式插槽 ,下面將分別說明每種插槽的作用

1. 普通插槽

普通插槽就是在元件中直接書寫文件結構這種元件的特點就是在插槽中會失去響應式,也就是說在插槽中使用了響應式資料將無法獲得更新,一般用來展示一次性資料

function Son(props,slot) {
  const { num } = props
  return {
    name: 'Son',
    render() {
      return (
        <div>
          {slot()} {num()}
        </div>
      )
    }
  }
}

export default function () {
  return {
    name: 'Dome',
    render() {
      const { data: $ } = this
      return (
        <div>
          <button onClick={() => $.num++}>num++</button>
          <Son num={() => $.num}>
            <span>Num: </span>
          </Son>
        </div>
      )
    },
    data: {
      num: 0
    },
    components: { Son },
  }
}

2. 函式插槽

函式插槽就是在元件中使用一個函式將文件結構返回出去,這種方式將不會失去資料的響應式,同時它還可以拿到元件中的一些資料在 Son 元件中向 slot 傳遞一個字串,在 Dome 元件中定義插槽的函式中可以透過 title 拿到並使用

function Son(_,slot) {
  return {
    name: 'Son',
    render() {
      return (
        <div>
          {slot("Num: ")}
        </div>
      )
    }
  }
}

export default function () {
  return {
    name: 'Dome',
    render() {
      const { data: $ } = this
      return (
        <div>
          <button onClick={() => $.num++}>num++</button>
          <Son>{(title) => (
            <span>{title} {$.num}</span>
          )}</Son>
        </div>
      )
    },
    data:()=>({
      num: 0
    }),
    components: { Son },
  }
}

3. 多插槽

在元件中可以使用多個插槽,在使用多插槽時需要使用 { } 雙花括號包裹,每個單獨插槽就使用一個 {} 包裹 ,slot 將得到一個陣列陣列中包含了每個插槽

function Son(_,slot) {
  return {
    name: 'Son',
    render() {
      return (
        <div>
          <div>插槽1 {slot[0]()}</div>
          <div>插槽2 {slot[1]()}</div>
        </div>
      )
    }
  }
}

export default function () {
  return {
    name: 'Dome',
    render() {
      return (
        <div>
          <Son>
             {<span>多插槽1</span>}
             {<span>多插槽2</span>}
          </Son>
        </div>
      )
    },
    components: { Son },
  }
}

9. proto 向原型新增屬性或方法

使用 proto 方法可以向建構函式的原型上新增屬性或方法,在創造例項前呼叫使用,有兩種使用方法,第一種每次只能新增一個方法或屬性,第二種使用物件形式可以新增多個方法或屬性,

import Bindview from "../../bindview"


// 使用一
Bindview.proto('Test', function(){
    console.log("Test")
})

// 使用二
Bindview.proto({
    Test1:"hello",
    Test2(){
        console.log("Test")
    }
})

new Bindview({
  el: '#Root',
  render(h) {
    return (
      <div>hello</div>
    )
  }
})

10.外掛

外掛通常用來為 Vue 新增全域性功能

外掛用來給 bindview 擴充功能,如新增全域性元件或全域性屬性,透過 use 方法來使用外掛, 傳入陣列可以使用多個外掛

!!! 外掛需要是一個函式或一個帶有 _install_ 方法的物件,它們會獲得 bindview 的構造器

import { Bindview } from "bindview@3"
import App from "./App";

import { history } from "bindview-router"

Bindview.use(history)

new Bindview({
  el: '#Root',
  render: () => (<App />),
  components: { App }
})

11.全域性元件

components 方法用來註冊全域性元件,在 new Bindview 之前全域性註冊的元件無需再註冊即可使用

import Bindview from "../../bindview@3"
import App from "./App";

Bindview.components("App",App) // 方式一
Bindview.components({ App }) // 方式二

new Bindview({
  el:"#Root",
  render: () => (<App />),
})

12. 動態元件

動態元件是在同一個位置根據不同的狀態顯示不同的元件,在動態元件中必須要有一個 id 引數並傳入一個不會改變的唯一 id

下面是一個簡單的動態元件例子

import { crateId } from "bindview"
import Dome1 from "./Dome1"
import Dome2 from "./Dome2"

export default function (props) {
  let { state } = props
  const [ a , b ] = crateId(2)

  return {
    name: 'Test',
    render() {
      return state() ? <Dome1 id={a} /> : <Dome2 id={b} />
    },
    components: { Dome1, Dome2 }
  }
}

生命週期鉤子

每個 Bindview 例項在被建立時都要經過一系列的初始化過程——例如,需要設定資料監聽、將例項掛載到 DOM 並在資料變化時更新 DOM 等。同時在這個過程中也會執行一些叫做 生命週期鉤子 的函式,這給了使用者在不同階段新增自己的程式碼的機會

Bindview生命週期鉤子 需要配置到 life 配置項中

生命週期鉤子 呼叫時
beforeInit 例項初始化前
created dom 建立後
updated 資料改變後
beforeDestroy 元件銷燬前

1. beforeInit

beforeInit 會在元件例項初始化時呼叫, 在該階段可以透過一個形參獲取到元件的配置物件透過修改配置物件可以修改最後被建立出來的例項,也可以在此階段透過 this 向元件例項上新增一些自定義的方法或資料

function Life() {
  return {
    name: "Lifecomponents",
    render() {
      return (
        <div>hello</div>
      )
    },
    life: {
		beforeInit(ConfigObj){
            console.log(ConfigObj)
        }
    }
  }
}

2. created

created 鉤子會在 render 配置項中的 虛擬DOM 被建立為 真實DOM 後呼叫,在此階段就可以透過 refs 得到 DOM 元素了

function Life() {
  return {
    name: "Lifecomponents",
    render() {
      return (
        <div ref="box">hello</div>
      )
    },
    life: {
		created(){
            console.log(this.refs['box'])
        }
    }
  }
}

3. updated

updated 鉤子會在 data 配置項中的資料被修改後呼叫

function Life() {
  return {
    name: "Lifecomponents",
    render() {
      return (
        <div>hello</div>
      )
    },
    life: {
		updated(){
            console.log("date發生改變")
        }
    }
  }
}

4. beforeDestroy

beforeDestroy 鉤子會在元件被解除安裝之前呼叫,在此階段可以登出事件監聽,和清空定時器

function Life() {
  return {
    name: "Lifecomponents",
    render() {
      return (
        <div>hello</div>
      )
    },
    life: {
		beforeDestroy(){
            // 清空定時器或登出事件監聽
        }
    }
  }
}

原型方法

Bindview 元件例項中有一些以 $ 開頭的原型方法,這些方法提供了一些方便開發的功能,這些方法中有些一般情況下只是用一次應為這些方法一般是是用來給元件新增一些配置的 如 新增全域性元件,下面將說明每個原型方法的作用和使用方法

1. $appendComponent

$appendComponent 追加元件,向已經建立好的元件中註冊新的元件在 created 生命週期中呼叫,一般來註冊非同步請求後的元件

export default function App() {
  return {
    name: 'App',
    render() {
      const { data: _ } = this
      return (
        <div id="App">
         {_.show?<div>佔位<div>:<Test id="UUID" />}
        </div >
      )
    },
    data: () => ({
      show: false
    }),
    life: {
     async created() {
        let module=await import("@/components/Test")
        this.$appendComponent("Test",module.default)
        this.data.show=true
      }
    }
  }
}

2. $mount

$mount 安裝元件, 將元件例項安裝到頁面上,方法需要傳入一個 DOM 選擇字串 ( '#Root' , '.Root' 等) 或是一個 DOM 元素

import { createApp,Bindview } from "../../bindview";
import App from "./App"

createApp(App).$mount("#Root")

// new Bindview({
// render: () => (<App />),
//   components: { App }
// }).$mount("#Root")

3. $remove

$remove 解除安裝元件,透過元件例項呼叫可解除安裝元件,解除安裝時會呼叫 beforeDestroy 生命週期鉤子

import { createApp } from "../../bindview";
import App from "./App"

const vm = createApp(App).$mount("#Root")

// 解除安裝元件
vm.$remove()

4. $mupdate

$mupdate 方法可以手動更新檢視,在修改一些沒有資料響應的資料但需要更新檢視時可以在修改後呼叫這個方法,或傳入一個函式,檢視更新會在回撥函式執行完後

export default function () {
  return {
    name: 'App',
    render() {
      const { methods: f } = this
      return (
        <div ref="Box" className="App">
          App
          <div>{f.datas()}</div>
          <button onClick={f.addData}>addDatas</button>
        </div>
      )
    },
    methods: {
      addData() {
        this.$mupdate(() => {
          this.datas++
        })
      },
      datas() {
        return this.datas
      }
    },
    life: {
      beforeInit() {
        this.datas = 0
      }
    }
  }
}

工具函式

Bindview 中不止有 Bindview 建構函式還提供了一些便於開發的工具函式

1. send

send 方法簡化元件間傳遞引數的方法,該方法需要傳入兩個引數 1.資料來源 2.資料項,該方法會返回一個物件物件中有兩個方法分被是get set 用來獲取和修改資料

import { Bindview, send } from "bindview"

// Dome 元件
function Dome(props) {

  let { num, arr } = props

  return {
    name: 'Dome',
    render() {
      const { methods:f } = this  
      return (
        <div>
          <div>Dome</div>
          <div>{num.get()}</div>
          <button onClick={f.set}>set</button>
          <div>{arr.get()}</div>
          <button onClick={f.setArr}>setArr</button>
        </div>
      )
    },
    methods: {
      set() {
        // 1. num.set(100)
        // 2. i 是資料
        num.set(i => {
          return ++i;
        })
      },
      setArr() {
        arr.set(100)
      }
    }
  }
}


export default function () {
  return {
    el: '#Root',
    render() {
      const { data: $ } = this
      return (
        <div>
          <div>App</div>
          <Dome num={send($, 'num')} arr={send($.arr, 1)} />
        </div>
      )
    },
    data: {
      num: 0,
      arr: [1, 2, 3, 4]
    },
    components: { Dome }
  }
}

2. createId

createId 函式用來建立多個唯一的 ID, 函式傳入一個數值返回一個長度為該數值的陣列陣列中包含了唯一 ID,不傳入將返回一個唯一的 ID ,一般配合動態元件使用

import { crateId } from "bindview"
import Dome1 from "./Dome1"
import Dome2 from "./Dome2"

export default function (props) {
  let { state } = props
  const [ a , b ] = crateId(2)

  return {
    name: 'Test',
    render() {
      return state() ? <Dome1 id={a} /> : <Dome2 id={b} />
    },
    components: { Dome1, Dome2 }
  }
}

3. createApp

createApp 用來建立元件例項,建立的元件例項需要透過 $mount 進行掛載, createApp 可以傳入兩個引數第一個引數是元件函式是必須的,第二個是 props 物件不是必須的,這個物件在元件函式的 props 可以得到

import { createApp } from "../../bindview";
import App from "./App"

createApp(App, { title: '這是props' }).$mount("#Root")