Pl Vue

组件化

组件声明

组件声明可以是一个函数或是一个类,当组件为类时,必须要有 render 方法返回 JSX。

// 函数组件
function Comp() {
  return <div>hello</div>
}
// 类组件
class Comp {
  render() {
    return <div>hello</div>
  }
}

响应式数据绑定

响应式数据一律以函数的形式返回,否则数据无法更新。

因为 plvue 在实现响应式数据时,并没有虚拟 DOM 的参与,用箭头函数将数据返回是最简单直接的,也是最高效的。

import { ref } from 'pl-vue';

function Comp() {
  const count = ref(0);

  return <div>
    <h1>{() => count.value}</h1>
    <button onclick={() => count.value++}></button>
  </div>
}

props 传递

  1. 原则上响应式必须确保为一个函数,并确保内部使用了响应式数据。所以,props 响应式数据也需要传递为函数,对应的节点才会重新渲染;
  2. 在组件之间数据传递上,如果传给子组件的为响应式数据(代理对象),即子组件收到为响应式数据;如果传递给子组件为引用值对象,即子组件也可修改该对象。PlVue 并无做过多干预,一切符合 JS 规则。
import { ref, PropsType } from 'pl-vue';

// 父组件
function App() {
  const count = ref(0);
  return <div>
    <Comp>插槽</Comp>
  </div>
}

// 子组件
type Props = PropsType<{
  count: () => number
}>
function Comp(props: Props) {
  return <div>
    <h1>{props.count}</h1>
    {props.children}
  </div>
}

双向数据绑定

该库并未实现双向数据绑定,而是通过 ref 来实现双向数据流。

import { PropsType, h, reactive, toRef } from "~/core";

function App() {
  const form = reactive({
    name: 0,
  })

  return <div>
    <Input model={toRef(form, 'name')} />
    <button onclick={() => form.name ++}>click</button>
  </div>
}

type Props = PropsType<{
  model: { value: number }  // 传递一个 ref,方便子组件做修改
}>
function Input(props: Props) {
  return <input value={() => props.model.value} oninput={e => props.model.value = e.target.value} />
}

暴露组件方法

import { ref, defineExpose } from 'pl-vue';

function App() {
  const compRef = ref();

  return <div>
    <Comp ref={compRef} />
    <button onclick={() => compRef.value.addCount()}>click</button>
  </div>
}

function Comp() {
  const count = ref(0);

  function addCount() {
    count.value++;
  }

  defineExpose({ addCount });

  return <h1>{() => count.value}</h1>
}

生命周期钩子

onBeforeMount

在组件挂载前执行,也就是说通过 DOM 选择器还访问不到 DOM 节点,但可以通过 ref 来修改节点信息。

import { onBeforeMount, ref } from 'pl-vue';

function App(props) {

  const elRef = ref<HTMLElement>();
  onBeforeMount(() => {
    console.log(document.getElementById('demo'));  //--> null
    console.log(elRef.value);  //--> <div id="demo">hello</div>
  });

  return <div id="demo" ref={elRef}>hello</div>
}

onMounted

组件挂载后执行。此时可以通过 DOM 选择器访问到 DOM 节点。

import { onMounted } from 'pl-vue';

function App(props) {

  onMounted(() => {
    console.log(document.getElementById('demo'));  //--> <div id="demo">hello</div>
  });

  return <div id="demo">hello</div>
}

onBeforeUnmount

组件卸载前执行。此时还能通过 DOM 选择器访问到 DOM 节点。

import { onBeforeUnmount } from 'pl-vue';

function App(props) {

  onBeforeUnmount(() => {
    console.log(document.getElementById('demo'));  //--> <div id="demo">hello</div>
  });

  return <div id="demo">hello</div>
}

onUnmounted

组件卸载后执行。此时通过 DOM 选择器已经访问不到 DOM 节点,因为整个组件节点已经被移除掉。

import { onUnmounted } from 'pl-vue';

function App() {

  onUnmounted(() => {
    console.log(document.getElementById('demo'));  //--> null
  });

  return <div id="demo">hello</div>
}

高阶组件

高阶组件作用

  1. 减少重复代码;
  2. 代码逻辑复用;
  3. 强化组件;
  4. 修改组件行为;
function HignOrderComp(props) {
  props.text = props.text.trim();
  return <Comp {...props} />
}

function Page() {
  return <div>
    <HignOrderComp text=' hello ' />
  </div>
}
function Comp(props) {
  return <div>{props.text}</div>
}

类组件

设计类组件的目的:为了暴露一些方法用来继承/重写组件的逻辑。

class Comp {
  count = ref(0);

  render = () => {  // render 函数为渲染界面的主函数
    return <div>{() => this.count.value}</div>
  }
}

class NewComp extends Comp {
  // 重写某些方法
}

function Page() {
  return <NewComp />
}

全局组件

全局组件必须通过 createApp 来创建应用

import { createApp } from 'pl-vue';

function Comp(props) {
  return <div>hello {props.text}</div>
}

function App() {
  return <div>
    <my-comp text='world' />
  </div>
}

const app = createApp();
app.component("my-comp", Comp);
const root = document.getElementById('root');
root.appendChild(app.render(<App />));