Vue3学习细节

一.声明式API编程

  • 组合式函数编程 

 使用<script setup lang="ts"></scriopt>语法糖可以省去export default defineComponent

函数式编程:抽取一个可复用的日期格式化函数时。这个函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出 。隐藏了内部实现,直接获取想要的值。

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

<script setup>
import { useMouse } from './mouse.js'

const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>
  • 多个组合式函数的复用 

 更酷的是,你还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。实际上,这正是为什么我们决定将实现了这一设计模式的 API 集合命名为组合式 API。

// event.js
import { onMounted, onUnmounted } from 'vue'

export function useEventListener(target, event, callback) {
//目标dom  事件  回调函数
  // 如果你想的话,
  // 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
  onMounted(() => target.addEventListener(event, callback))
  onUnmounted(() => target.removeEventListener(event, callback))
}
// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'

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

  useEventListener(window, 'mousemove', (event) => {
    x.value = event.pageX
    y.value = event.pageY
  })

  return { x, y }
}

二.常用钩子函数

  • computed计算属性

响应式或者其他数据在computed里面改变后,监听数据也会改变

const formlistruleoption = computed(() => {
  const selectedOption = typeValueOptions.formList.find((i) => i.value === state.formListValue);
  return selectedOption ? [selectedOption.rule, selectedOption.option] : [null, null];
});


  • props

 const props = defineProps<{ book: Book }>()

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})

props.foo // string
props.bar // number | undefined
</script>

 defineProps实现的是运行时声明,因为defineProps内部传递的参数在运行时会调用

export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

 解构赋值props

  • watch的用法 
watch(oldvalue,(newvalue)=>{

})

watch(
  () => state.formListValue, // 要监听的变量或对象
  (value, oldValue) => {    // 回调函数,value 和 oldValue 分别表示变化后和变化前的值
    console.log('值变化-------??', value);
    typeValueOptions.formList.forEach((i) => {
      if (i.value == value) {
        state.rule = i.rule
        state.option = i.option
      }
    })
    console.log('值变化-------???', state);

  },
  {
    deep: true,             // 是否深度监听对象内部变化(默认为 false)
    immediate: true,        // 是否在初始绑定时立即运行回调(默认为 false)
    flush: 'post',          // 变化时机是否延迟推迟到下一次主线任务执行之后(默认为 'pre')
    // ... 其他选项参数
  }
);
  • watch和watchEffect的区别 

 watch是惰性执行,只有监听的数据值发生变化时才会执行,需要监听传递的对象;watchEffect不是,每次代码加载watchEffect都会执行,不需要监听传递的对象,会自动追踪函数内部使用的所有的响应式数据。

const state = reactive({
  count:0,
name:'tom'
})


watch(state,()=>{
 console.log(`Count is ${state.count}`)
})  


watchEffect(() => {
      console.log(`Count is ${state.count}`)
      console.log(`Name is ${state.name}`)
    })
  • inject和provide的用法和区别 

inject 接收数据  , 可以从任何父组件接收数据,数据可以是对象或者是基本数据类型

provide 分发数据 , 父组件向所有子组件分发数据,数据同样可以使对象或者基本数据类型,这两者的使用会让父子传值更加快捷,在封装组件时更加灵活

三.响应式对象

     ref和reactive

对比与vue2,vue3使用ref和reactive代理对象生成响应式数据,目的是实现按需地对数据进行加载,前者是可以接受任何值类型,后者只适用于对象 (包括数组和内置类如 Map 和 Set

  • ref的使用和常用场景
<script setup>
import { ref } from 'vue'

// 给每个 todo 对象一个唯一的 id
let id = 0

const newTodo = ref('')
const todos = ref([
  { id: id++, text: 'Learn HTML' },
  { id: id++, text: 'Learn JavaScript' },
  { id: id++, text: 'Learn Vue' }
])

function addTodo() {
  todos.value.push({ id: id++, text: newTodo.value })
  newTodo.value = ''
}

function removeTodo(todo) {
  todos.value = todos.value.filter((t) => t !== todo)
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo">
    <button>Add Todo</button>    
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>

实例中使用ref将字符串,对象数组装变成响应式数据类型,访问内容的时候需要用.value

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value

顶层属性的意思就是object,不是object.foo

ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性; 简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中。

const obj = {
  foo: ref(1),
  bar: ref(2)
}

// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)

// 仍然是响应式的
const { foo, bar } = obj

跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包。 

const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)

// 推导得到的类型:Ref<number | undefined> 支持泛型参数
const n = ref<number>()

当ref被嵌套时,会自动解包,当新的ref给旧的赋值时,会被替换掉 

  • reactive的使用和常用场景

仅对对象类型有效(对象、数组和 MapSet 这样的集合类型), Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失;

const state = reactive({ count: 0 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++

 就是说,局部变量解构属性的时候,属性丢失响应性,但是原来的响应式对象不受影响

  • toRef的使用场景
  • emit使用方法

// 声明触发的事件

const emit = defineEmits(['response'])

// 带参数触发

emit('response', 'hello from child')

  • unref的使用场景 

尽管其响应性不依赖 ref,组合式函数仍可接收 ref 参数。如果编写的组合式函数会被其他开发者使用,你最好在处理输入参数时兼容 ref 而不只是原始的值。unref() 工具函数会对此非常有帮助 

import { unref } from 'vue'

function useFeature(maybeRef) {
  // 若 maybeRef 确实是一个 ref,它的 .value 会被返回
  // 否则,maybeRef 会被原样返回
  const value = unref(maybeRef)
}

四.状态管理 (pinia)

pinia是Vue3中使用的状态管理工具,类似于Vue2中的vuex,但是区别在于它不再需要通过set函数去修改状态值,它只需要直接修改 


import { defineStore } from 'pinia';

// 取名是唯一的
export const useFormListStore = defineStore('useFormList', {
	state: () => ({
		hasTableData: false
	}),
	getters: {
      // 由state中数据派生
      
	},
	actions: {

	}
});


import { useFormListStore } from '/@/store/modules/formList.ts'
 console.log(store.hasTableData)
 

 对ts响应式对象声明的泛型,对内部对象类型赋值

export interface IFormData {
  /**
   * 创建时间
   */
  createTime?: Date;
  /**
   * 创建人
   */
  createUser?: number;
  /**
   * 编号
   */
  fromId?: number;
  /**
   * 表单名称
   */
  fromName?: string;
  /**
   * 表单备注
   */
  fromRemark?: string;
  /**
   * 1 删除
   */
  isDelete?: string;
  params?: { [key: string]: any };
  /**
   * 备注
   */
  remark?: string;
  searchValue?: string;
  /**
   * 更新时间
   */
  updateTime?: Date;
  /**
   * 更新人
   */
  updateUser?: number;
}

   对象类型赋值和限制泛型的方法:
  const obj = reactive({
    data : {} as IFormData 
})
obj.data.append('key',value)

五.路由跳转传参方式

  • path+query

如下代码传递了键值都是groupId对应的参数

this.$router.push({

                // path: '',

                name: "DesignHello",

                // query: {

                //  groupId

                // },

                params: {

                    groupId

                }

            });
  • name+query或params

 如上代码传递了键值都是groupId对应的参数,params在路由地址中不显示,query会显示,params通过route.params.groupId获取传递的值,query直接通过route.groupId获取传递的值


六.JeecgBoot3+Vue3的组件使用 

1.BasicForm

  • 监听某一项的数据变化
  componentProps: ({ formModel, formActionType }) => {
      console.log('业务对象表单的值', formModel, formActionType);
      return {
        dictCode: 'flow_business_type',
        onChange: (value) => {
          console.log('业务对象表单的值', value);
        }
      }
    },

  

配置对应表单项的componentProps属性即可,返回值包含了所需要的更改事件和字典配置,以及表单属性和配置相关

  • 监听某一项的数据值是否有输入
watch(form.field,(newvalue)=>{
 console.log('22222222',newvalue)
})
  • 校验某一项输入,通过判断是否在数组中
​
{
  field:'userName'
, dynamicRules: ({ model, schema }) => {
        const allusernames = toRaw(store.AlluserName);
        // console.log('输入信息', model, store.AlluserName);
        console.log('输入信息', schema);
        return [{
          required: true,
          message: '请输入用户账号'
        }, {
          validator: (_, value) => {
            const isexists = allusernames.includes(value);
            return new Promise<void>((resolve, reject) => {
              if (isexists) {
                reject('该用户账号已存在');
              } else {
                resolve();
              }
            })
          }

        }];
      }

}

​
  • setFieldsValue()的用法
 setFieldsValue({ nodeType, nodeName, signType, sendMessage })//如果field的命名和获取到的属性一致可以这样写
  • 记一次render属性配置
  render: ({ model, field }) => {
      let value = model[field]
      return h('span', {
        onChange: (value: string) => {
          model[field] = value;
        },
      }, NodeTypes1[value]);
    },
// 渲染的值放在第三个参数中

如果是逗号拼接的字符串转数组,需要注意要在第三个参数配置要显示的项
    render: ({ model, field }) => {
      let str = model[field].toString()
      let value = str.split(',');
      console.log('渲染', value)

      return h('div', { style: { display: 'inline-block' } }, [
        ...value.map((v) => {
          return h(Tag, { style: { marginRight: '8px' } }, sendMessageEnums[v]);
        })
      ]);
    }
  •  枚举类型的定义需要注意的问题

export enum sendMessageEnums {

  phone = '电话',

  email = '邮箱',

  weixin = '微信'

}

最好是一边字符串,一边是常量或者字面量

  • 转字符串的方法
let str = String(model[field]);
let str = `${model[field]}`;//es6模版
  •  插槽的使用方法
 {
    field: 'nodeName',
    component: 'Input',
    label: '审核人',
    componentProps: {
      disabled: true,
      bordered: false
    },
    slot: 'nodeName'}

//界面
<BasicForm>

   <template #nodeName ></template>
</BasicForm>

2.BasicTable

如果需要自定义表格数据,可以通过BasicTable的data-source属性绑定响应式数据,然后需要绑定render(如果datasource变化)内置方法重新渲染表格,不这样会报错,应为初始化的时候datasource并没有值,具体事例如下;


//html
<BasicTable :canResize="false" ref="tableRef" :data-source="tableData" :columns="columns" rowKey="id"
			:reload="getDatasource()">
			<template #action="{ record }">
				<TableAction :actions="getActions(record)" />
			</template>

		</BasicTable>
// js
async function getDatasource() {
	await getFormList().then((res) => {
		tableData.value = res.data.data
	}).catch((err) => {
		console.log('err', err);
	})
}

这样操作会反复加载渲染表格,不推荐,一般是在api上面配置请求函数
  •  表格中slot插槽用法
 //表格配置colums

export const columns: BasicColumn[] = [ {
    title: '发起人',
    dataIndex: 'subUserName',
    width: 70,
    slots: { customRender: 'subUserName' },

  }]


// 表格内部
<BasicTable>
 <template #subUserName="{ record }">
   //可渲染内容
</tempolate>
</BasicTable>
  • 常见的使用场景 

 配置表格的属性和配置项,实现列表的数据展示,以及需要注意的情况

  <BasicTable @register="registerTable" :rowSelection="rowSelection">

      <template #bodyCell="{ column, record }">

        <template v-if="column.key === 'startUser'">
          <div style="display: flex; align-items: center;">
            <img :src="record.startUser.alisa ? record.startUser.alisa : `src/assets/images/logo.png`"
              style="width: 35px;height: 35px; margin-right: 20px;" />
            <span>{{ record.startUser.name }}</span>
          </div>

        </template>
        <template v-else-if="column.key === 'recordState'">
          <span>
            <a-tag v-if="record.recordState == `1`" color="blue"> 处理中</a-tag>
            <a-tag v-if="record.recordState == `2`" color="yellow">正常完成</a-tag>
            <a-tag v-if="record.recordState == `3`" color="red">拒绝结束</a-tag>
            <!-- <a-tag v-if="record.recordState == `4`" color="yellow">已结束</a-tag> -->
          </span>

        </template>
      </template>

      <template #action="{ record }">
        <TableAction :actions="getActions(record)" />
      </template>
    </BasicTable>


// 列表页面公共参数、方法
const { tableContext } = useListPage({
  designScope: 'position-template',
  tableProps: {
    api: getFlowRecordList,
    columns: columns,
    formConfig: {
      schemas: searchFormSchema,
    },
    rowKey: 'id',
    beforeFetch: (params) => {
      params.page = { pageNo: params.pageNo, pageSize: params.pageSize };
      params.query = [{ columnName: 'flowName', value: '', whereType: 'eq' }];
      return params;
    },
    actionColumn: {
      width: 70,
      fixed: 'right',
    },
    rowSelection: {
      getCheckboxProps: (record) => ({
        disabled: true, // 设置禁用状态
      }),
    }
  },
});
  •  自定义查询条件
/**
 * 查询列表
 * @param params
 */
export const getFlowRecordList = async (params) => {
	console.log('params', params);

	const data = {
		query: [
			{ flowName: params.flowName },
			{ keyWords: params.keyWords },
			{ handleUser: store.userInfo.userId ? store.userInfo.userId : '' }
		],
		page: {
			current: params.pageNo,
			size: params.pageSize
		}
	}
	const { records } = await defHttp.post({ url: Api.list, data });
	return records;
};

需要在参数接口里转化传递的参数
  • 自定义渲染表格字典 
 // income_method是收款类别字典,可以替换成其他内置的字典
 export const columns =  [{
    title: '收付款类别', dataIndex: 'subjectSign', width: 100,
    customRender: ({ text }) => render.renderDict(text, 'income_method'),

  }]

3.BasicModal 

  • Hooks函数式组件界面

顾名思义,函数钩子,它的本质是一种重要的代码组合机制,最开始是React提出,Hooks 提供了一种单一的、功能性的方式来处理共享逻辑,并有可能减少代码量,传递的参数一般是响应式对象

  • 具体实际用例
  const todos = ref([
      { id: 1, text: 'Learn Vue3' },
      { id: 2, text: 'Build an app' },
      { id: 3, text: 'Deploy to production' }
    ])

    const newTodoText = ref('')

    function addTodo() {
      const newTodo = {
        id: todos.value.length + 1,
        text: newTodoText.value
      }
      todos.value.push(newTodo)
      newTodoText.value = ''
    }

 addTodo函数是hooks函数不带参数传递 

 4.Description 

  {
    field: 'incrFee',
    label: '递增金额', 
    render: (curVal, data) => {
      if( data.increasesType == '2') return `${curVal}元`;
      return `无`;
    },

  },
DescItem的render传递两个参数,那项的值和单条数据的值
  • ant-design-vue的样式修改(deep不生效后的处理)

需要准确找到需要修改样式的类名,而且需要保证原始样式没有!important

  • 常用函数 

str.split(',')//字符串转数组

arr.join(',')//数组转字符串

arr.filter(i=>i.id==id)//返回值是boolean会筛选值为true的项

arr.map(i=>{ let newItem = { ...i,j:'' } return newItem})//返回值不会影响原生数组,他创建了一个新的数组

arr.forEach(i => { i.j = '';});//这样处理会修改arr原始数组的数据
arr.splice(0,length-1)//截取数组除了最后一位的所有,取头不取尾

arr.find(i=>i>3) //返回满足条件的第一项,不能返回数组,这是和filter最大的区别

  • JeecgBoot中JSelectMultiple组件setFieldValues时不生效(尽量不适用这个,采用Select和mode:mutiple替代)
  • 记一次封装部门用户选择,对应新系统接口(覆写JSelectUserByDept 组件,没有必要,直接替换官方接口就行,JSelectUser也可以单独使用)

4.用户选择组件(JSelectUser) 

  • 记一次单独使用组件经历
<a-button shape="round" @click="openHandle">选择</a-button>
			<UserSelectModal rowKey="userId" @register="registerSelUserModal" @getSelectResult="onSelectOk" />

// 注册用户选择框
const [registerSelUserModal, { openModal: openUserModal, closeModal: closeUserModal }] = useModal()

// 打开用户选择框
function openHandle() {
	openUserModal();
}
// 选择用户成功
function onSelectOk(options, values) {
	formData.selected = values.join(',')
	formData.showNickName = options[0].nickName
	console.log('选择的用户', options, values, formData.selected)
	closeUserModal()
	//处理业务逻辑
}

七.Vue3的进阶使用方法

1.函数式组件的自定义和封装

<script setup lang='ts'>

import { ref, h } from "vue"

/**
 * Implement a functional component :
 * 1. Render the list elements (ul/li) with the list data
 * 2. Change the list item text color to red when clicked.
*/
const ListComponent = (props) => {
  console.log('属性',props)
   return h('ul',props.list.map((o,i)=>{
     return h('li',{
       key:i,
       onClick:()=>props.onToggle(i),//这里面函数需要加上on然后函数要首字母大写
       style:props['active-index'] == i ? {color:'red'} : null,
     
     },o.name)
   }))
}

const list = [{
  name: "John",
}, {
  name: "Doe",
}, {
  name: "Smith",
}]

const activeIndex = ref(0)

function toggle(index: number) {
  activeIndex.value = index
}

</script>

<template>
  <list-component
    :list="list"
    :active-index="activeIndex"
    @toggle="toggle"
  />
</template>

2.对话框+表单模板

<template>
      <BasicModal v-bind="$attrs" :title="modalTitle" 
            :height="500"
            @register="register" 
            @ok="handleSubmit"
            cancelText="取消" 
            okText="确认"
            destroyOnClose >
            <BasicForm @register="registerForm" />

      </BasicModal>
</template>
<script lang="ts" setup>
import { ref, useAttrs, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { TerMinationForm } from '../contract.data'
import { TerminationContract, editBatchStopDate } from '../contract.api'

const attrs = useAttrs();

const emit = defineEmits(['success'])
const modalTitle = ref('退租合同')

/**
 * 下拉菜单触发key
 */
const key = ref('')
const billIds = ref('')

 //自定义接受参数
const props = defineProps({
    //是否禁      用页面
    contractId: { type: String, default: '' },
});

//注册弹框
const [register, { closeModal }] = useModalInner( async (data) => {
      console.log('表单值',data)
      await resetFields();

})

//表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate, updateSchema, resetSchema }] = useForm({
    schemas: TerMinationForm,
    labelWidth: 100,
    showResetButton:false,
    showSubmitButton: false,
});

async function handleSubmit(){
      const values = await validate();
      console.log('表单值',values)

      try {
            let params = {
                  ...values,
                  billIds: unref(billIds),
                  contractId: props.contractId,
            }
            await TerminationContract(params,closeModal());
            emit('success')
            ;
      } catch (error) {
            
      }
}
</script>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值