2-3-7 Vue3 组件封装

展示类组件封装

当属性的绘制完全依赖属性时,封装变得非常容易:

function Button({style, text} : {style : any, text : string}){
    return <button style={style}>text</button>
}

容器类组件

如果一个组件是容器,`vue` 是通过slot来处理的。

const ButtonWithSlots = (_ : any, context : any) => {
  return <button>{context.slots.default()}</button>
}

在`@vue/babel-plugin-jsx`中,slots被封装到了渲染函数的第二个参数中。 `slots.default` 代表了默认的`slot` 。使用时:

export const ButtonExample02 = () => {
  return <ButtonWithSlots><span>你好!</span></ButtonWithSlots>
}

当然可以有多个`slot` ,不过建议不要这样,因为这样阅读起来不是非常方便(美观):

const A = (props, { slots }) => (
  <>
    <h1>{ slots.default ? slots.default() : 'foo' }</h1>
    <h2>{ slots.bar?.() }</h2>
  </>
);

const App = {
  setup() {
    const slots = {
      bar: () => <span>B</span>,
    };
    return () => (
      <A v-slots={slots}>
        <div>A</div>
      </A>
    );
  },
};

// or

const App = {
  setup() {
    const slots = {
      default: () => <div>A</div>,
      bar: () => <span>B</span>,
    };
    return () => <A v-slots={slots} />;
  },
};

// or you can use object slots when `enableObjectSlots` is not false.
const App = {
  setup() {
    return () => (
      <>
        <A>
          {{
            default: () => <div>A</div>,
            bar: () => <span>B</span>,
          }}
        </A>
        <B>{() => "foo"}</B>
      </>
    );
  },
};

输入组件

vue Input表单的一个完整的例子

import { ref, defineComponent, PropType, watch } from "vue"

const Input = defineComponent({
  props: {
    onChange: {
      type: Function as PropType<(v: any) => void>,
      required: false,
    },
    value: {
      type: String,
      required: false,
    },
  },
  setup(props) {
    const input = ref<HTMLInputElement | null>(null)

    watch(
      () => props.value,
      () => {
        const ipt = input.value!
        if(ipt.value !== props.value) {
          ipt.value = props.value || ""
        }
      }
    )
    return () => {
      return (
        <input onInput={e => {
          props.onChange &&
            props.onChange(
              (e.target as HTMLInputElement).value
            )
        }} value={props.value} ref={input} />
      )
    }
  },
})

export const FormExample = defineComponent({
  setup(){
    let formData = {
      username : '张三',
      info : "xxx"
    }

    const ver = ref(0)

    return () => {
      return <div key={ver.value}>
        <button onClick={() => {

          console.log(formData)
          formData = {
            username : '张三',
            info : "xxx"
          }
          ver.value ++
        }}>重置/提交</button>
        <Input
          value={formData.username}
          onChange={(v) => formData.username = v}
        />
        <Input
          value={formData.info}
          onChange={(v) => formData.info = v}
        />
      </div>
    }
  }

})

对表单数据的封装

可以对表单数据进行一定的封装,使用起来更加方便:

import {
  ref,
  defineComponent,
  PropType,
  watch,
} from "vue"

import {Input} from '../components/Input'
import {useForm} from '../hooks/useForm'

export const FromExample02 = defineComponent({
  setup() {
    const {form, ver} = useForm({
      username: "张三",
      info: "xxx",
    })

    watch(form.getValues(), () => {
      console.log('form data changed', form.getValues().value)
    })

    return () => (
      <div>
        <button
          onClick={() => {
            const values = form.getValues().value
            console.log("submit", values)
            form.setValues({
              username: "张三",
              info: "xxx",
            })
            ver.value++
          }}
        >
          提交/重置
        </button>
        <Input
          {...form.username}
        />
        <Input
          {...form.info}
        />
      </div>
    )
  },
})

封装公共行为

封装事件和计算

function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function handler(e: MouseEvent) {
    x.value = e.x
    y.value = e.y
    console.log('move', e.x, e.y)
  }

  window.addEventListener("mousemove", handler)

  onScopeDispose(() => {
    window.removeEventListener("mousemove", handler)
  })

  return { x, y }
}

公共Scroll事件的封装

封装一个滚动到底部的判定

import { defineComponent } from "vue"

class ScrollDescriptor {
  private left: number = 0
  private top: number = 0
  private scrollHeight: number = 0
  private offsetHeight: number = 0

  private scrollToBottomHandlers: Function[] = []

  public onScrollToBottom(handler: Function) {
    this.scrollToBottomHandlers.push(handler)
    return () => {
      this.scrollToBottomHandlers =
        this.scrollToBottomHandlers.filter(
          (x) => x !== handler
        )
    }
  }

  private triggerScrollToBottom() {
    this.scrollToBottomHandlers.forEach((h) => h())
  }

  public update(
    left: number,
    top: number,
    offsetHeight: number,
    scrollHeight: number
  ) {
    this.left = left
    this.top = top
    this.scrollHeight = scrollHeight
    this.offsetHeight = offsetHeight
    if (this.bottomReached()) {
      this.triggerScrollToBottom()
    }
  }

  public bottomReached() {
    return this.top + this.offsetHeight >= this.scrollHeight
  }
}

const useScroll = () => {
  const scrollInfo = new ScrollDescriptor()

  const scrollHandler = <T extends HTMLElement>(
    e: Event
  ) => {
    const scroller = e.currentTarget as T
    const left = scroller.scrollLeft
    const top = scroller.scrollTop
    scrollInfo.update(
      left,
      top,
      scroller.offsetHeight,
      scroller.scrollHeight
    )
  }

  return {
    onScroll: scrollHandler,
    info: scrollInfo,
  }
}

export const ScrollerExample = defineComponent({
  setup() {
    const { onScroll, info } = useScroll()

    info.onScrollToBottom(() => {
      console.log('here---')
    })
    return () => (
      <div
        onScroll={onScroll}
        style={{
          height: '600px',
          width: '400px',
          overflow: "scroll",
        }}
      >
        <div
          style={{
            height: '800px',
            width: "100%",
            background: "red",
          }}
        ></div>
        <div
          style={{
            height: '800px',
            width: "100%",
            background: "blue",
          }}
        ></div>
        <div
          style={{
            height: '800px',
            width: "100%",
            background: "yellow",
          }}
        ></div>
      </div>
    )
  },
})

封装请求和逻辑

import {ref, defineComponent} from 'vue'
import Mock from 'mockjs'


type Product = {
  name : string
}
function useProducts() {
  const list = ref<Product[] | null>(null)

  async function request() {
    list.value = Mock.mock({
      "array|1-10" : [{
        name: /iphone|xiaomi|hongmi|huawei|sanxing|google|ms/,
      }],
    }).array
    console.log(list.value)
  }
  request()

  return {
    list,
    reload: request,
  }
}


export const ProductList = defineComponent({

  setup() {

    const {list, reload} = useProducts()

    return () => {
      return <div>

        <button onClick={reload}>reload</button>
        <ul>
          {list.value?.map( (x, i) => {
            return <li key={i}>{x.name}</li>
          })}
        </ul>

      </div>

    }
  }

})

1

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值