当前位置:   article > 正文

2-3-7 Vue3 组件封装_vue3组件封装

vue3组件封装

展示类组件封装

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

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

容器类组件

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

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

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

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

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

  1. const A = (props, { slots }) => (
  2. <>
  3. <h1>{ slots.default ? slots.default() : 'foo' }</h1>
  4. <h2>{ slots.bar?.() }</h2>
  5. </>
  6. );
  7. const App = {
  8. setup() {
  9. const slots = {
  10. bar: () => <span>B</span>,
  11. };
  12. return () => (
  13. <A v-slots={slots}>
  14. <div>A</div>
  15. </A>
  16. );
  17. },
  18. };
  19. // or
  20. const App = {
  21. setup() {
  22. const slots = {
  23. default: () => <div>A</div>,
  24. bar: () => <span>B</span>,
  25. };
  26. return () => <A v-slots={slots} />;
  27. },
  28. };
  29. // or you can use object slots when `enableObjectSlots` is not false.
  30. const App = {
  31. setup() {
  32. return () => (
  33. <>
  34. <A>
  35. {{
  36. default: () => <div>A</div>,
  37. bar: () => <span>B</span>,
  38. }}
  39. </A>
  40. <B>{() => "foo"}</B>
  41. </>
  42. );
  43. },
  44. };

输入组件

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

  1. import { ref, defineComponent, PropType, watch } from "vue"
  2. const Input = defineComponent({
  3. props: {
  4. onChange: {
  5. type: Function as PropType<(v: any) => void>,
  6. required: false,
  7. },
  8. value: {
  9. type: String,
  10. required: false,
  11. },
  12. },
  13. setup(props) {
  14. const input = ref<HTMLInputElement | null>(null)
  15. watch(
  16. () => props.value,
  17. () => {
  18. const ipt = input.value!
  19. if(ipt.value !== props.value) {
  20. ipt.value = props.value || ""
  21. }
  22. }
  23. )
  24. return () => {
  25. return (
  26. <input onInput={e => {
  27. props.onChange &&
  28. props.onChange(
  29. (e.target as HTMLInputElement).value
  30. )
  31. }} value={props.value} ref={input} />
  32. )
  33. }
  34. },
  35. })
  36. export const FormExample = defineComponent({
  37. setup(){
  38. let formData = {
  39. username : '张三',
  40. info : "xxx"
  41. }
  42. const ver = ref(0)
  43. return () => {
  44. return <div key={ver.value}>
  45. <button onClick={() => {
  46. console.log(formData)
  47. formData = {
  48. username : '张三',
  49. info : "xxx"
  50. }
  51. ver.value ++
  52. }}>重置/提交</button>
  53. <Input
  54. value={formData.username}
  55. onChange={(v) => formData.username = v}
  56. />
  57. <Input
  58. value={formData.info}
  59. onChange={(v) => formData.info = v}
  60. />
  61. </div>
  62. }
  63. }
  64. })

对表单数据的封装

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

  1. import {
  2. ref,
  3. defineComponent,
  4. PropType,
  5. watch,
  6. } from "vue"
  7. import {Input} from '../components/Input'
  8. import {useForm} from '../hooks/useForm'
  9. export const FromExample02 = defineComponent({
  10. setup() {
  11. const {form, ver} = useForm({
  12. username: "张三",
  13. info: "xxx",
  14. })
  15. watch(form.getValues(), () => {
  16. console.log('form data changed', form.getValues().value)
  17. })
  18. return () => (
  19. <div>
  20. <button
  21. onClick={() => {
  22. const values = form.getValues().value
  23. console.log("submit", values)
  24. form.setValues({
  25. username: "张三",
  26. info: "xxx",
  27. })
  28. ver.value++
  29. }}
  30. >
  31. 提交/重置
  32. </button>
  33. <Input
  34. {...form.username}
  35. />
  36. <Input
  37. {...form.info}
  38. />
  39. </div>
  40. )
  41. },
  42. })

封装公共行为

封装事件和计算

  1. function useMouse() {
  2. const x = ref(0)
  3. const y = ref(0)
  4. function handler(e: MouseEvent) {
  5. x.value = e.x
  6. y.value = e.y
  7. console.log('move', e.x, e.y)
  8. }
  9. window.addEventListener("mousemove", handler)
  10. onScopeDispose(() => {
  11. window.removeEventListener("mousemove", handler)
  12. })
  13. return { x, y }
  14. }

公共Scroll事件的封装

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

  1. import { defineComponent } from "vue"
  2. class ScrollDescriptor {
  3. private left: number = 0
  4. private top: number = 0
  5. private scrollHeight: number = 0
  6. private offsetHeight: number = 0
  7. private scrollToBottomHandlers: Function[] = []
  8. public onScrollToBottom(handler: Function) {
  9. this.scrollToBottomHandlers.push(handler)
  10. return () => {
  11. this.scrollToBottomHandlers =
  12. this.scrollToBottomHandlers.filter(
  13. (x) => x !== handler
  14. )
  15. }
  16. }
  17. private triggerScrollToBottom() {
  18. this.scrollToBottomHandlers.forEach((h) => h())
  19. }
  20. public update(
  21. left: number,
  22. top: number,
  23. offsetHeight: number,
  24. scrollHeight: number
  25. ) {
  26. this.left = left
  27. this.top = top
  28. this.scrollHeight = scrollHeight
  29. this.offsetHeight = offsetHeight
  30. if (this.bottomReached()) {
  31. this.triggerScrollToBottom()
  32. }
  33. }
  34. public bottomReached() {
  35. return this.top + this.offsetHeight >= this.scrollHeight
  36. }
  37. }
  38. const useScroll = () => {
  39. const scrollInfo = new ScrollDescriptor()
  40. const scrollHandler = <T extends HTMLElement>(
  41. e: Event
  42. ) => {
  43. const scroller = e.currentTarget as T
  44. const left = scroller.scrollLeft
  45. const top = scroller.scrollTop
  46. scrollInfo.update(
  47. left,
  48. top,
  49. scroller.offsetHeight,
  50. scroller.scrollHeight
  51. )
  52. }
  53. return {
  54. onScroll: scrollHandler,
  55. info: scrollInfo,
  56. }
  57. }
  58. export const ScrollerExample = defineComponent({
  59. setup() {
  60. const { onScroll, info } = useScroll()
  61. info.onScrollToBottom(() => {
  62. console.log('here---')
  63. })
  64. return () => (
  65. <div
  66. onScroll={onScroll}
  67. style={{
  68. height: '600px',
  69. width: '400px',
  70. overflow: "scroll",
  71. }}
  72. >
  73. <div
  74. style={{
  75. height: '800px',
  76. width: "100%",
  77. background: "red",
  78. }}
  79. ></div>
  80. <div
  81. style={{
  82. height: '800px',
  83. width: "100%",
  84. background: "blue",
  85. }}
  86. ></div>
  87. <div
  88. style={{
  89. height: '800px',
  90. width: "100%",
  91. background: "yellow",
  92. }}
  93. ></div>
  94. </div>
  95. )
  96. },
  97. })

封装请求和逻辑

  1. import {ref, defineComponent} from 'vue'
  2. import Mock from 'mockjs'
  3. type Product = {
  4. name : string
  5. }
  6. function useProducts() {
  7. const list = ref<Product[] | null>(null)
  8. async function request() {
  9. list.value = Mock.mock({
  10. "array|1-10" : [{
  11. name: /iphone|xiaomi|hongmi|huawei|sanxing|google|ms/,
  12. }],
  13. }).array
  14. console.log(list.value)
  15. }
  16. request()
  17. return {
  18. list,
  19. reload: request,
  20. }
  21. }
  22. export const ProductList = defineComponent({
  23. setup() {
  24. const {list, reload} = useProducts()
  25. return () => {
  26. return <div>
  27. <button onClick={reload}>reload</button>
  28. <ul>
  29. {list.value?.map( (x, i) => {
  30. return <li key={i}>{x.name}</li>
  31. })}
  32. </ul>
  33. </div>
  34. }
  35. }
  36. })

1

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/701588
推荐阅读