前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端系列第10集-实战篇

前端系列第10集-实战篇

作者头像
达达前端
发布2023-10-08 19:04:31
2350
发布2023-10-08 19:04:31
举报
文章被收录于专栏:达达前端

用户体验:性能,交互方式,骨架屏,反馈,需求分析等

组件库:通用表单,表格,弹窗,组件库设计,表单等

项目质量:单元测试,规范,监控,报警,monorepo等

性能优化:性能指标,代码更快,文件加载更快,框架优化,优化方案分析等

普通项目:登录注册,布局,增删改查等

研发效率:脚手架,组件库,开发规范,联调效率,自动化等

vue3+ts:vue3,vite,pinia,组件库,vue-router等

工具库:axios,工程化,工具库,pnpm,typescript等

大数据量

如果直接渲染1W行列表,不出意外你的页面就要卡了,比较常见的优化方案就是虚拟滚动,就是只渲染你能看到的视窗中的几十行,然后通过监听滚动来更新这几十个dom

  • 可视区的高度固定 viewHeight (clientHeight
  • 每个列表高度height (固定
  • 可视区域的数据索引start和end (scrollTop / height
  • 基于startIndex计算出offset偏移(scrollTop - (scrollTop % height);
  • 渲染数据 & 监听滚动事件
代码语言:javascript
复制
// 列表容器的dom
  const container = useRef<HTMLDivElement>(null)
  // 开始位置
  const [start, setStart] = useState(0)
  // 视图中的数据
  const [visibleData, setVisibleData] = useState<VirtualProps['list']>([])
  // 控制偏移量
  const [viewTransfrom, setViewTransfrom] = useState('translate3d(0,0,0)')
  useEffect(() => {
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度
    const visibleCount = Math.ceil(viewHeight / HEIGHT) // 视窗内有几个元素
    const end = start + visibleCount
    setVisibleData(list.slice(start, end))
  }, [])
  function handleScroll(e: React.UIEvent<HTMLDivElement, UIEvent>) {
    const scrollTop = e.currentTarget.scrollTop // 滚动的距离
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度

    const start = Math.floor(scrollTop / HEIGHT)
    const end = start + Math.ceil(viewHeight / HEIGHT)
    setVisibleData(list.slice(start, end))
    setStart(start)
    setViewTransfrom(`translate3d(0,${start * HEIGHT}px,0)`)
  }
代码语言:javascript
复制
// 预估高度60
const PREDICT_HEIGHT = 60
  // 不定高数组,维护一个位置数据
const [positions, setPosition] = useState<{ top: number;height: number }[]>([])

// 渲染数组之后,更新positions数组
Array.from(listDom?.children).forEach((node, index) => {
  const { height } = node.getBoundingClientRect()
  // console.log(start+index, node.id)
  if (height !== positions[start + index].height) {
    setPosition((prev) => {
      const newPos = [...prev]
      newPos[start + index].height = height
      for (let k = index + 1; k < prev.length; k++)
        newPos[k].top = newPos[k - 1].top + newPos[k - 1].height
      return newPos
    })
  }
})
}, [visibleData])

文件上传

  1. 文件切片 + 秒传 + 暂停
  2. 文件计算hash值,就像文件的身份证号,用来问后端有没有切片存在
  3. 计算hash的卡顿 可以使用web-worker,时间切片,抽样Hash三种解决方案
  4. 上传文件切片
代码语言:javascript
复制
async handleVerify(req, res) {
 const data = await resolvePost(req)
 const { filename, hash } = data
 const ext = extractExt(filename)
 const filePath = path.resolve(this.UPLOAD_DIR, `${hash}${ext}`)
 
 //文件是否存在
 let uploaded = false
 let uploadedList = []
 if (fse.existsSync(filePath)) {
  uploaded = true
 } else {
  // 文件没有完全上传完毕,但是可能存在部分切片上传完毕了
  uploadedList = await getUploadedList(path.resolve(this.UPLOAD_DIR, hash))
 }
 res.end(
  JSON.stringify(
   uploaded,
   uploadedList // 过滤诡异的隐藏文件
  })
 )
}

web-worker计算md5

代码语言:javascript
复制
async calculateHash(chunks) {
 return new Promise(resolve => {
  // web-worker 防止卡顿主线程
  this.container.workder = new Worker("/hash.js");
  this.container.workder.postMessage({ chunks });
  // 等通知
  this.container.workder.onmessage = e => {
   const { progress, hash } = e.data
   this.hashProgress = Number(progress.toFixed(2));
   if (hash) {
    resolve(hash);
   }
  };
 });
}

const workLoop = async deadline => {
 // 有任务,并且当前帧还没有结束
 while (count < chunks.length && deadline.timeRemaining() > 1) {
  await appendToSpark(chunks[count].file);
  count++;
  // 没有了 计算完毕
  if (count < chunks.length) {
   // 计算中
   this.hashProgress = Number(
    ((100 * count) / chunks.length).toFixed(2)
   );
  } esle {
   this.hashProgress = 100;
   // 计算任务结束
   resolve(spark.end());
  }
 }
 // 当前帧没有时间了,说明浏览又渲染任务了
 window.requestIdleCallback(workLoop);
 };
 window.requestIdleCallback(workLoop);
});
代码语言:javascript
复制
function limit(maxCount) {
 // 任务队列
 let queue = []
 let activeCount = 0
 
 const next = () => {
  // 下一个任务
  activeCount--
  if(queue.length>0) {
   queue.shift()()
  }
 }
 const run = async (fn,resolve,args) => {
  // 执行一个函数
  activeCount++
  const result = (async() => fn(...args))()
  resolve(result)
  await result
  next()
 }
 const push = async (fn,resolve,args) => {
  queue.push(run.bind(null, fn, resolve, args))
  if (activeCount < maxCount && queue.length > 0) {
   // 队列没满,并且还有任务,启动任务
   queue.shift()()
  }
 }
 let runner = (fn, ...args) => {
  return new Promise((resolve) => {
   push(fn, resolve, args)
  })
 }
 return runner
}

async function asyncPool({
 limit,
 items,
 fn
}) {
 const promises = []
 const pool = new Set()
 for (const item of items) {
  const promise = fn(item)
  promises.push(promise)
  pool.add(promise)
  const clean = () => pool.delete(promise)
  promise.then(clean, clean)
  if (pool.size >= limit) await Promise.race(pool)
 }
 return Promise.all(promises)
}

完整的构建打包流程/服务(统一的脚手架、上线服务等)、完整的测试环境、前端错误日志管理系统(收集、统计、报警)、前端资源离线化管理、前端资源增量下载服务以及针对Node应用的日志(完整调用链)、性能和错误监控平台等等。

runner的执行方式有很多种, 目前最流行的就是作为一个docker容器,其内部集成了gitlab的一些基础环境, 注册阶段就是将其与gitlab主任务做关联(runner通常不跟gitlab服务器部署在同一台服务器),而yaml中配置的任务,就是在runner中具体执行, 然后将结果发送回gitlab服务器。

最后项目需要在setttings中开启enable shared runner或者specific runner.

使用Node搭建服务,托管静态资源,以及代理请求的转发。

runner中执行yaml中的task

  • 资源构建 针对测试环境打包: npm run build -e test
  • 上传资源到node 服务器。 将该服务抽离为npm 包, 执行festaging-scripts命令,上传的资源有两类:
    • 构建出的静态资源
    • 必要的请求代理配置
代码语言:javascript
复制
function buildUrl(prefix) {}

var originXHROPEN = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
 return originXHROpen.call(this, method, buildUrl(url, '${prefix}'), async, user, password);
 
if (window.fetch) {
 var originFetch = window.fetch;
 window.fetch = function () {
  var input = arguments[0];
  if (typeof input === 'string') {
   arguments[0] = buildUrl(input, '${prefix}');
  }
  return originFetch.apply(this, arguments);
 };
}

统一项目新建入口、项目开发模板,项目开发流程。节省新成员上手成本。

团队成员可以通过输入项目名、GitLab 组、项目模板等字段直接创建 GitLab 仓库,并根据选择的模板及名称等信息在已创建的 GitLab 仓库里进行项目初始化。

  • 量化团队代码质量,统计团队工作量,监测业务吞吐量变化等。
  • 在团队中推行 Commit 提交规范。
  • 获取团队成员的 Git Commit 信息,并存入数据库,以 Commit 信息数据为基础做数据统计分析。
  • fix、feat、style、refactor 等开头的 Commit 可绘制饼图进行统计对比

需要注意的有

  • 有些操作或导致 Commit 重复,所以对于同一个人的 Commit 需要做去重。
  • GitLab Events 只会存近一年的数据。所以运行当天也只能统计近一年的代码。
  • 一定要按人进行 Push 时间划分,这样第一次运行之后,后面就可以只取上次取的最后一次 Push 的时间之后的 Commit 了。请求数可以减少很多。
  • 执行过代码之后发现了一些问题,比如:团队成员误操作将 node_modules 文件夹上传等。这造成了统计代码行数过多,解决办法是过滤掉大于 10000 行(这个可以自由指定)的 commit 。

持续集成基本概念

在传统开发过程中,代码的集成工作通常是在所有工程师们工作完成后进行的,需要单独构建,这往往会花费大量的时间和精力。持续集成是一种将集成工作放在软件开发阶段的做法,以便更加有规律地构建、测试和集成代码。

持续集成可以在开发人员提交了新代码后,立即进行构建、单元测试,可以根据测试结果确定新代码或配置环境是否正确。

WeChate8e849f3d9d0a434ad212cf2ae4c8cdc.png

服务器相关操作

安装docker

具体可参考官网:docs.docker.com/engine/inst…[1]

安装gitlab-runer

docker方式安装

代码语言:javascript
复制
# 拉取镜像
docker pull gitlab/gitlab-runner:latest
# 运行镜像
docker run -d --name gitlab-runner --restart always \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -v /srv/gitlab-runner/config:/etc/gitlab-runner \
        gitlab/gitlab-runner
添加用户组及权限
代码语言:javascript
复制
# 添加 用户组及用户
useradd -m -g gitlab-runner gitlab-runner

# 查看系统用户
sudo vim /etc/passwd

# 将下图蓝框内的数字改为0:0,和root保持一致
注册gitlab-runner
代码语言:javascript
复制
# 使用一次性容器来注册 gitlab-runer, --rm 容器推出时清理用户数据
docker run --rm  -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register

image.png

代码语言:javascript
复制
# 输入域名或者服务器ip地址,就是步骤三的url,如果gitlab和要部署的服务器地址不一致,需要做个地址映射哦,自行百度下
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): 
# 输入token
Please enter the gitlab-ci token for this runner:
# 输入runner描述,可写可不写
Please enter the gitlab-ci description for this runner:
# 给这个Runner指定tags,可以写多个,英文逗号隔开即可
Please enter the gitlab-ci tags for this runner (comma separated):
# 选择Runner是否接受未指定tags的任务,稍后可修改,默认值为false,可写可不写
Whether to run untagged builds [true/false]:
# 选择是否为当前项目锁定Runner,通常用于被指定为某个项目的Runner,默认值为true,可写可不写
Whether to lock the Runner to current project [true/false]: 
# 选择Runner executor(Runner执行器),这里我们选docker哈
Please enter the executor: docker, shell, virtualbox, kubernetes, docker-ssh, parallels, ssh, docker+machine, docker-ssh+machine: 
docker
# docker版本选最新版
Please enter the default Docker image (e.g. ruby:2.6):
docker:latest

# 好了到这一步,看到输出以下语句,就算注册完了,接下来去CI/CD界面下的Runners选项里,看看有没有成功;
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

docker restart gitlab-runner

 cd ~/.ssh # 查看是否存在密钥
 ssh-keygen -t rsa # 生成密钥
 cat id_rsa    #查看私钥
 cat id_rsa.pub # 查看公钥

image.png

在项目根目录下新增文件gitlab-ci.yml,将以下代码粘贴过去,然后提交代码到test分支

代码语言:javascript
复制
test:build:
    stage: test
    script:
      - docker build -t fast_api .
      - if [ $(docker ps -aq --filter name=trunkverse_service) ]; then docker rm -f trunkverse_service; fi
      - docker run -d -p 8098:8098 --restart=always --name fast_api fast_api 
      - echo 'docker run 完成'
    only:
      - test # 指定test分支一更新则立即构建
    tags:
      - fastApi # 对应每个runner注册时定义的tag

test:deploy:
  image: alpine:3.7
  stage: deploy
  script:
    - echo "http://mirrors.aliyun.com/alpine/v3.7/main/" > /etc/apk/repositories # 下载镜像
    - apk add --no-cache rsync openssh # 安装rsync openssh
    - mkdir -p ~/.ssh
    - echo "$SSH_KEY_PRIVATE" >> /root/.ssh/id_rsa
    - echo "$SSH_KEY_PUB" >> /root/.ssh/id_rsa.pub
    - chmod 700 /root/.ssh/
    - chmod 600 /root/.ssh/id_rsa.pub
    - chmod 600 /root/.ssh/id_rsa
    - echo -e "Host *\n\t StrictHostKeyChecking no \n\n" > ~/.ssh/config
    - rsync -av --delete   ./  $SERVER_HOST:$SERVER_PATH
  only:
    - test
  tags:
    - fastApi

Git 管理方案

master 为生产分支 develop 为开发分支

develop 分支下存在多个功能分支,以 develop 做为基础切出,并会合并回 develop

版本分支 下存在多个环境的版本历史,以版本控制的严格程度分化为多个子分支

master 孑然一身,只有存在紧急 Bug 时,才会有 hotfix分支切入并合并回 master 与 develop

image.png

代码语言:javascript
复制
# 阶段
stages:
  - install
  - build
  - deploy
  # 缓存 node_modules 减少打包时间,默认会清除 node_modules 和 dist
cache:
  paths:
    - node_modules/

# 安装依赖
install:
  stage: install
  tags: # runner 标签(注册runner时设置的)
    - webpack-vue-cicd
  only:
    changes:
      - package.json
  script: # 执行脚本
    yarn

# 拉取项目,打包
build:
  stage: build # 阶段名称 对应,stages
  tags: # runner 标签(注册runner时设置的,可在 admin->runner中查看)
    - webpack-vue-cicd
  script: # 脚本(执行的命令行)
    - cd ${CI_PROJECT_DIR} # 拉取项目的根目录
    - npm install # 安装依赖
    - npm run build # 运行构建命令
  only:
    - main #拉取分支
  artifacts: # 把 dist 的内容传递给下一个阶
    paths:
      - dist/

# 部署
deploy:
  stage: deploy # 阶段名称 对应,stages
  tags: # runner 标签(注册runner时设置的)
    - webpack-vue-cicd
  script: # 脚本(执行的命令行)
    - rm -rf /www/wwwroot/webpack_vue_cicd/*
    - cp -rf ${CI_PROJECT_DIR}/dist/* /www/wwwroot/webpack_vue_cicd/ # 把包完成,复制 dist 下的文件到对应的项目位置
代码语言:javascript
复制
stages:
  - test
  - build
  - deploy
  
test:
  stage: test
  tags:
    - shell-g-fe-runner
  script:
    - npm install --no-optional --registry=https://registry.npm.taobao.org/
    - npm run lint
代码语言:javascript
复制
stages:
  - test
  - build
  - deploy
  
test:
  stage: test
  tags:
    - shell-g-fe-runner
  script:
    - npm install --no-optional --registry=https://registry.npm.taobao.org/
    - npm run lint
    
build:
  stage: build
  tags:
    - shell-g-fe-runner
  script:
    - sudo npm rebuild node-sass
    - sudo npm run build
  only:
    - master
    - /^beta\/.*$/
    - /^release\/.*$/

GitLab 的 CI 程序同时包含缓存机制,如果你想把你的编译产物缓存下来

代码语言:javascript
复制
build:
  stage: build
  tags:
    - shell-g-fe-runner
  script:
    - sudo npm rebuild node-sass
    - sudo npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 60 mins
代码语言:javascript
复制
deploy_test:
  stage: deploy
  tags:
    - shell-g-fe-runner
  only:
    - /^beta\/.*$/
  environment:
    name: Test
    url: http://test.vue.com/
  script:
    - cp -R dist/* /data/html/vue-com/test/
    
deploy_uat:
  stage: deploy
  tags:
    - shell-g-fe-runner
  only:
    - /^release\/.*$/
  environment:
    name: Uat
    url: https://uat.vue.com/
  script:
    - cp -R dist/* /data/html/vue-com/uat/

deploy_prod:
  stage: deploy
  tags:
    - shell-g-fe-runner
  only:
    - master
  environment:
    name: Production
    url: https://vue.com/
  script:
    - cp -R dist/* /data/html/vue-com/prod/

安装 rsync

代码语言:javascript
复制
deploy_prod:
  stage: deploy
  tags:
    - shell-g-fe-runner
  only:
    - master
  environment:
    name: Production
    url: https://vue.com/
  script:
    - rsync -ravtz --delete --password-file=/data/auth/rsync.pwd dist/* 192.168.1.1::vue-com-prod/

ref() 标注类型有三种方式:

  1. 通过泛型参数的形式来给 ref()增加类型
代码语言:javascript
复制
import { ref } from 'vue'

const initCode = ref<string | number>('200')
  1. 如果是遇到复杂点的类型,可以自定义 interface 然后泛型参数的形式传入
代码语言:javascript
复制
import { ref } from 'vue'

interface User {
  name: string
  age: string | number
}

const user = ref<User>({
  name:'xxx',
  age: 20
})
  1. 通过使用 Ref 这个类型为 ref 内的值指定一个更复杂的类型
代码语言:javascript
复制
import { ref } from 'vue'
import type { Ref } from 'vue'

const initCode: Ref<string | number> = ref('200')

reactive() 返回一个对象的响应式代理。

reactive()标注类型有两种方式:

  1. 直接给声明的变量添加类型
代码语言:javascript
复制
import { reactive } from 'vue'

interface User {
  name: string
  age: string | number
}

const user:User = reactive({
  name:"xxx",
  age:'20'
})
  1. 通过泛型参数的形式来给 reactive()增加类型
代码语言:javascript
复制
import { reactive } from 'vue'

interface User {
  name: string
  age: string | number
}

const user = reactive<User>({
  name:"xxx",
  age:'20'
})

computed()标注类型有两种方式:

  1. 从其计算函数的返回值上推导出类型
代码语言:javascript
复制
import { ref, computed } from 'vue'

const count = ref<number>(0)

// 推导得到的类型:ComputedRef<string>
const user = computed(() => count.value + 'xxx')
  1. 通过泛型参数显式指定 computed() 类型
代码语言:javascript
复制
const user = computed<string>(() => {
  // 若返回值不是 string 类型则会报错
  return 'xxx'
})

为了在声明 props 选项时获得完整的类型推断支持,我们可以使用 defineProps API,它将自动地在 script setup 中使用

  1. 从它的参数中推导类型:
代码语言:javascript
复制
const props = defineProps({
  name: { type: String, required: true },
  age: Number
})
  1. 通过泛型参数来定义 props 的类型
代码语言:javascript
复制
const props = defineProps<{
  name: string
  age?: number
}>()

定义成一个单独的 interface

代码语言:javascript
复制
interface Props {
  name: string
  age?: number
}

const props = defineProps<Props>()
代码语言:javascript
复制
// vite.config.js
export default {
  plugins: [
    vue({
      reactivityTransform: true
    })
  ]
}

通过对 defineProps() 的响应性解构来添加默认值:

代码语言:javascript
复制
<script setup lang="ts">
interface Props {
  name: string
  age?: number
}

const { name = 'xxx', age = 100 } = defineProps<Props>()
</script>

为了在声明 emits 选项时获得完整的类型推断支持,我们可以使用 defineEmits API,它将自动地在 script setup 中使用

defineEmits() 标注类型直接推荐泛型形式

代码语言:javascript
复制
import type { GlobalTheme } from 'naive-ui'

const emit = defineEmits<{
  (e: 'setThemeColor', val: GlobalTheme): void
}>()
为 defineExpose() 标注类型

defineExpose() 类型推导直接使用参数类型自动推到即可

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'

const name = ref<string>('xxx')

defineExpose({
  name
})

provide()供给一个值,可以被后代组件注入

为 provide() 标注类型, Vue 提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型

代码语言:javascript
复制
import type { InjectionKey } from 'vue'

// 建议声明 key (name) 放到公共的文件中
// 这样就可以在 inject 的时候直接导入使用
const name = Symbol() as InjectionKey<string>

provide(name, 'xxx') // 若提供的是非字符串值会导致错误

以上方式是通过定义 key 的类型来标注类型的,还有一种方式直接 key 采用字符串的形式添加

代码语言:javascript
复制
provide('name', 'xxx')

inject()注入一个由祖先组件或整个应用供给的值

provide() 的 key 的类型是声明式提供的话(provide()类型标注的第一种形式)

inject() 可以直接导入声明的 key 来获取父级组件提供的值

代码语言:javascript
复制
// 由外部导入
const name = Symbol() as InjectionKey<string>

const injectName = inject(name)

如果 provide() 的 key 直接使用的字符串形式添加的, 需要通过泛型参数声明

代码语言:javascript
复制
const injectName = inject<string>('name')

模板 ref 需要通过一个显式指定的泛型参数和一个初始值 null 来创建:

代码语言:javascript
复制
<img ref="el" class="logo" :src="Logo" alt="" />

const el = ref<HTMLImageElement | null>(null)
代码语言:javascript
复制
<!-- Child.vue -->
<script setup lang="ts">
const handleLog = () => console.log('xxx')

defineExpose({
  open
})
</script>

<!-- parent.vue -->
<script setup lang="ts">
import Child from './Child.vue'

// 为子组件 ref 声明类型
const child = ref<InstanceType<typeof Child> | null>(null)

// 调用子组件中的方法
const getChildHandleLog = () => {
  child.value?.handleLog()
}
</script>

Vue组件引入

代码语言:javascript
复制
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
</script>

<template>
  <HelloWorld msg="Vite + Vue" />
</template>

defineProps 在有两种定义方式

代码语言:javascript
复制
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number,
});

<script setup lang="ts"> 
    interface Props { 
        foo: string 
        bar?: number 
    } 
    const props = defineProps<Props>() 
</script>

默认值

代码语言:javascript
复制
// 第二种带默认值props
export interface ChildProps {
  foo: string
  bar?: number
}
const props = withDefaults(defineProps<ChildProps>(), {
   foo: "xxx"
   bar?: 3
})
代码语言:javascript
复制
<script setup lang="ts">
interface Book {
  title: string;
  author: string;
  year: number;
}

const props = defineProps<{
  book: Book;
}>();
</script>
代码语言:javascript
复制
import type { PropType } from 'vue'

interface Book {
  title: string;
  author: string;
  year: number;
}

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

defineEmits和defineProps获取父组件传过来值和事件

代码语言:javascript
复制
// 第一种获取事件方法
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 第二种获取事件方法
const emit = defineEmits(["dosth"])

ref一般用于基本的数据类型,比如string,boolean

reactive一般用于对象

不能修改reactive设置的值

代码语言:javascript
复制
let state = reactive({ count: 0 }) 
// the above reference ({ count: 0 }) is no longer being tracked (reactivity connection // is lost!) 
// 这里state如果重新赋值以后,vue就不能双向绑定
state = reactive({ count: 1 })

useAttrs 可以获取父组件传过来的id和class等值。 useSlots 可以获得插槽的内容。

代码语言:javascript
复制
<template>

    <div class="father">{{ fatherRef }}</div>

    <Child :fatherRef="fatherRef" @changeVal="changeVal" class="btn" id="111">

        <template #test1>

        <div>1223</div>

        </template>

    </Child>

</template>

<script setup lang="ts">

import { ref } from "vue";

import Child from "./Child.vue";

const fatherRef = ref("1");

function changeVal(val: string) {

    fatherRef.value = val;

}

</script>

<style lang="scss" scoped>

.father {

    margin-top: 40px;

    margin-bottom: 40px;

}

.btn {

    font-size: 20px;

    color: red;

}

</style>


<template>

    <!-- <div class="child">{{ props.fatherRef }}</div> -->

    <div v-bind="attrs">

        <slot name="test1">11</slot>

        <input type="text" v-model="inputVal" />

    </div>

</template>

<script setup lang="ts">

import { computed, useAttrs, useSlots } from "vue";

const props = defineProps<{

    fatherRef: string;

}>();

const emits = defineEmits(["changeVal"]);

const slots = useSlots();

const attrs = useAttrs();

console.log(122, attrs, slots);

const inputVal = computed({

    get() {

        return props.fatherRef;

    },

    set(val: string) {

        emits("changeVal", val);

    },

});

</script>


<style lang="scss" scoped>

.child {

}

</style>

自定义focus指令,命名就是vMyFocus,使用的就是v-my-focus

代码语言:javascript
复制
<script setup lang="ts">
const vMyFocus = {
  onMounted: (el: HTMLInputElement) => {
    el.focus();
    // 在元素上做些操作
  },
};
</script>
<template>
  <input v-my-focus value="111" />
</template>

使用defineExpose子组件传父组件

代码语言:javascript
复制
<template>

    <div class="child"></div>

</template>


<script setup lang="ts">

import { ref, reactive } from "vue";

function doSth() {

    console.log(333);

}

defineExpose({ doSth });

</script>


<style lang="scss" scoped>

.child {

}

</style>
代码语言:javascript
复制
<template>

<div class="father" @click="doSth1">222</div>

    <Child ref="childRef"></Child>

</template>

<script setup lang="ts">

import { ref, reactive } from "vue";

import Child from "./Child.vue";

const childRef = ref();

function doSth1() {

    childRef.value.doSth();

}

</script>

<style lang="scss" scoped>

.father {

}

</style>

当从父组件向子组件传props的时候,必须使用toRefs或者toRef进行转一下 如果不使用toRefs转一次的话,当父组件中的props改变的时候,子组件如果使用了Es6的解析,会失去响应性。

解决办法

  1. 使用const { fatherRef } = toRefs(props);
  2. 在模版中中使用props.fatherRef

1. 可以在子组件中使用computed,实现双向绑定

代码语言:javascript
复制
<template>

    <div class="father">{{ fatherRef }}</div>

    <Child :fatherRef="fatherRef" @changeVal="changeVal"></Child>

</template>

<script setup lang="ts">

import { ref } from "vue";

import Child from "./Child.vue";

const fatherRef = ref("1");

function changeVal(val: string) {

    fatherRef.value = val;

}

</script>


<style lang="scss" scoped>

.father {

    margin-top: 40px;

    margin-bottom: 40px;

}

</style>

<template>

    <!-- <div class="child">{{ props.fatherRef }}</div> -->

    <input type="text" v-model="inputVal" />

</template>

<script setup lang="ts">

import { computed } from "vue";

const props = defineProps<{

    fatherRef: string;

}>();

const emits = defineEmits(["changeVal"]);


const inputVal = computed({

    get() {

        return props.fatherRef;

    },

    set(val: string) {

        emits("changeVal", val);

    },

});

</script>

<style lang="scss" scoped>

.child {

}

</style>
代码语言:javascript
复制
<template>

    <Child :modelValue="searchText" @update:modelValue="changeVal"> </Child>

</template>

<script setup lang="ts">

import { ref } from "vue";

import Child from "./Child.vue";

const searchText = ref(1);

function changeVal(val: number) {

    searchText.value = val;

}

</script>

<style lang="scss" scoped>

.father {

    margin-top: 40px;

    margin-bottom: 40px;

}

.btn {

    font-size: 20px;

    color: red;

}

</style>
代码语言:javascript
复制
<template>
  <input v-model="modelValue" />
  <Child
    :modelValue="test"
    @update:modelValue="changeTest"
    v-if="modelValue > 2"
  ></Child>
</template>

<script setup lang="ts">
import { computed, useAttrs, useSlots, ref } from "vue";
const props = defineProps<{
  modelValue: number;
}>();
const test = ref(0);
function changeTest(val: number) {
  test.value = val;
}

// const emits = defineEmits(["changeVal"]);
</script>

<style lang="scss" scoped>
.child {
  position: relative;
}
</style>

仓库地址:https://github.com/webVueBlog/WebGuideInterview

参考资料

[1]

https://docs.docker.com/engine/install/ubuntu/: https://link.juejin.cn/?target=https%3A%2F%2Fdocs.docker.com%2Fengine%2Finstall%2Fubuntu%2F

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 大数据量
  • 文件上传
  • 持续集成基本概念
  • 服务器相关操作
    • 安装docker
      • 安装gitlab-runer
        • 添加用户组及权限
          • 注册gitlab-runner
          • Git 管理方案
            • 为 defineExpose() 标注类型
            • Vue组件引入
            • defineEmits和defineProps获取父组件传过来值和事件
            • 使用defineExpose子组件传父组件
            • 1. 可以在子组件中使用computed,实现双向绑定
              • 参考资料
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档