Vue2:组件高级(上)

news2024/11/24 11:22:45

Vue2:组件高级(上)

Date: May 20, 2023
Sum: 组件样式冲突、data函数、组件通信、props、组件生命周期、vue3.x中全局配置axios


目标:

能够掌握 watch 侦听器的基本使用

能够知道 vue 中常用的生命周期函数

能够知道如何实现组件之间的数据共享

能够知道如何在 vue3.x 的项目中全局配置 axios

前言:以下使用较老的axios,否则会报错

npm i axios@0.21.1 -S


组件之间的样式冲突

样式冲突问题:

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题

导致组件之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的

② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

思考:如何解决组件样式冲突的问题

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

Untitled



style 节点的 scoped 属性

为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

Untitled

注意:父组件与子组件都要的style都要加上scoped

默认情况:写在组件中的样式会 全局生效 一因此很容易造成多个组件之问的样式冲突问题

  1. 全局样式:默认组件中的样式会作用到全局
  2. 局部样式:可以给组件加上 scoped 属性,可以让样式只作用于当前组件

原理

  1. 当前组件内标签都被添加data-v-hash值 的属性
  2. css选择器都被添加 [data-v-hash值] 的属性选择器

最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

Untitled



/deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器。

Untitled

注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。

Vue3中的样式结构::deep(标签)

<style lang="less" scoped>
  p {
    color: red;
  }

  :deep(h3) {
    color: blue
  }
</style>



data必须是一个函数

原因

目的:保证每个组件实例,维护独立的一份数据对象。

每次创建新的组件实例,都会新执行一次data 函数,得到一个新对象。

Untitled

举例

data()函数能够保证每个组件的数据是独立的

Untitled




组件通信

基础概念:

基础概念

组件通信:指组件与组件之间的数据传递

  • 组件的数据是独立的,无法直接访问其他组件的数据。
  • 想使用其他组件的数据,就需要组件通信

组件之间的关系

在项目开发中,组件之间的关系分为如下 3 种:

① 父子关系 ② 兄弟关系 ③ 后代关系

AB是父子关系,BC有一个共同的父级节点,故二者为兄弟关系。B和EFI都为特殊的兄弟关系。

A和DGH属于后代关系

Untitled



父子组件之间的数据共享

父子组件之间的数据共享又分为:

① 父 -> 子共享数据 ② 子 -> 父共享数据 ③ 父 <-> 子双向数据同步

通信过程:

1-父组件通过 props 将数据传递给子组件

2-子组件利用 $emit 通知父组件修改更新

Untitled


父向子组件共享数据

父组件通过 v-bind 属性绑定向子组件共享数据。同时,子组件需要使用 props 接收数据。

Untitled

案例:

App.vue

<template>
  <div>
    <h1>MyAPP -- {{ count }}</h1>
    <!-- 1.给最爱你标签,添加属性的方式,传值 -->
    <button @click="count += 1">父+1</button>
    <my-son :num="count"></my-son>
  </div>
</template>

<script>
import MySon from './Son.vue'
export default {
  name: 'MyApp',
  components: {
    MySon,
  },
  data() {
    return {
      count: 0,
    }
  }
}
</script>

MySon.vue

<template>
  <div>
    <!-- 3.渲染使用 -->
    <h2>MySon -- {{ num }}</h2>
  </div>
</template>

<script>
export default {
  name: 'MySon',
  // 2. 通过props进行接收
  props: ['num']
}
</script>

效果:

Untitled


子向父组件共享数据

子组件通过自定义事件的方式向父组件共享数据。

具体步骤:

子组件:

  1. 声明自定义事件 2. 数据变化时,触发自定义事件

父组件:

  1. 监听子组件的自定义事件 numchang 2.通过形参,接收子组件传递过来的数据

Untitled

案例:

App.vue

<template>
  <div>
    <h1>MyAPP -- {{ count }}</h1>
    <button @click="count += 1">父+1</button>
		<!-- 1. 监听子组件的自定义事件 numchange -->
    <my-son :num="count" @numchange="getNum"></my-son>
  </div>
</template>

<script>
import MySon from './Son.vue'
export default {
  name: 'MyApp',
  components: {
    MySon,
  },
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    getNum(num) { // 2. 通过形参,接收子组件传递过来的数据
      this.count = num
    }
  }
}
</script>

Son.vue

<template>
  <div>
    <h2>MySon -- {{ num }}</h2>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  name: 'MySon',
  props: ['num'],
  emits: ['numchange'], //1. 声明自定义事件
  methods: {
    add() {
      this.$emit('numchange', this.num + 1) //2,数据变化时,触发自定义事件
    }
  }
}
</script>

效果:

Untitled


父子组件之间数据的双向同步

父组件在使用子组件期间,可以使用 v-model 指令维护组件内外数据的双向同步:

具体步骤:

  1. 父组件向子组件的props中传递数据
    1. 这里通过 v-model 方式进行双向数据绑定,维护组件两方数据同步
  2. 子组件声明emits属性,组件内的元素需要以 update: 的方式开头,这里需要更新哪个数据,就把相应数据的值丢过来,比如number
    1. 通过 $emits 的方式将数据发送出去

Untitled

好处:父组件中不用再监听自定义事件,也不用再额外定义事件处理函数

案例:

  • Code:

    App.vue

    <template>
      <div>
        <h1>MyAPP -- {{ count }}</h1>
        <button @click="count += 1">父+1</button>
        <my-son v-model:num="count" ></my-son>
      </div>
    </template>
    
    <script>
    import MySon from './Son.vue'
    export default {
      name: 'MyApp',
      components: {
        MySon,
      },
      data() {
        return {
          count: 0,
        }
      },
    }
    </script>
    

    Son.vue

    <template>
      <div>
        <h2>MySon -- {{ num }}</h2>
        <button @click="add">+1</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'MySon',
      props: ['num'],
      emits: ['update:num'],
      methods: {
        add() {
          // this.$emit('numchange', this.num + 1)
          this.$emit('update:num', this.num + 1)
        }
      }
    }
    </script>
    

效果:

Untitled



兄弟组件之间的数据共享

2023Vue教程的做法

**作用:**非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)

步骤

1-创建一个都能访问的事件总线 (空Vue实例)

注:把这个放在utils下的EventBus.js中

import Vue from 'vue'
const Bus = new Vue()
export default Bus

2-A组件(接受方),监听Bus的 $on事件

// 先导入Bus
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
	// 再从 created 阶段就监听 $on 事件
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}

3-B组件(发送方),触发Bus的$emit事件

注:这个在组件内

import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}

图示

注意:这是个一对多的发送

Untitled

案例:传递A组件数据给B组件

Untitled

  • Code:

    BaseA.vue

    <template>
      <div class="base-a">
        我是A组件(接受方)
        <p>{{msg}}</p>  
      </div>
    </template>
    
    <script>
    import Bus from '../utils/EventBus'
    export default {
      data() {
        return {
          msg: '',
        }
      },
      created() {
        Bus.$on('sendMsg', (msg) => {
          // console.log(msg)
          this.msg = msg
        })
      },
    }
    </script>
    
    <style scoped>
    .base-a {
      width: 200px;
      height: 200px;
      border: 3px solid #000;
      border-radius: 3px;
      margin: 10px;
    }
    </style>
    

    BaseB.vue

    <template>
      <div class="base-b">
        <div>我是B组件(发布方)</div>
        <button @click="sendMsgFn">发送消息</button>
      </div>
    </template>
    
    <script>
    import Bus from '../utils/EventBus'
    export default {
      methods: {
        sendMsgFn() {
          Bus.$emit('sendMsg', '今天天气不错,适合旅游')
        },
      },
    }
    </script>
    
    <style scoped>
    .base-b {
      width: 200px;
      height: 200px;
      border: 3px solid #000;
      border-radius: 3px;
      margin: 10px;
    }
    </style>
    

2021Vue教程的做法

兄弟组件之间实现数据共享的方案是 EventBus。

可以借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。

示意图如下:

Untitled

理解:在数据接收方调用on方法来声明自定义事件,在数据发送方通过emit方法来触发emit事件

3.1 安装 mitt 依赖包

在项目中运行如下的命令,安装 mitt 依赖包:

npm install mitt@2.1.0

3.2 创建公共的 EventBus 模块

在项目中创建公共的 eventBus 模块如下:

// eventBus.js

// 导入 mitt 包
import mitt from 'mitt'
// 创建 EventBus 的实例对象
const bus = mitt()

// 将 EventBus 的实例对象共享出去
export default bus

3.3 在数据接收方自定义事件

在数据接收方,调用 bus.on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件。

示例代码如下:

// 导入 eventBus.js 模块, 得到共享的bus对象

export default {
	data() {return { count: 0}},
	created() {
		// 在created生命周期函数中声明自定义事件
		// 调用 bus.on 方法注册一个自定义事件,通过事件处理函数的形参数接收数据
		bus.on('countChange', (count) => {
			this.count = count
		})
	}
}

3.4 在数据接发送方触发事件

在数据发送方,调用 bus.emit(‘事件名称’, 要发送的数据) 方法触发自定义事件。示例代码如下:

// 导入 eventBus.js 模块,得到共享的 bus 对象
import bus from './eventBus.js'

export default {
	data() {return { count: 0}},
	methods: {
		addCount() {
			this.count++
			bus.emit('countChange', this.count) // 调用 bus.emit() 方法触发自定义事件,并发送数据
		}	
	}
}

案例:

  • Code:

    Left.vue

    <template>
      <div>
        <h2>Left--数据发送方--num的值为: {{ count }}</h2>
        <button @click="addCount">+1</button>
      </div>
    </template>
    
    <script>
    import bus from './eventBus.js'
    export default {
      name: 'MyLeft',
      data() {
        return {
          count: 0,
        }
      },
      methods: {
        addCount() {
          this.count++
          bus.emit('countChange', this.count)
        }
      }
    }
    </script>
    

    Right.vue

    <template>
      <div>
        <h2>Right--数据接收方--num的值为:{{ num }}</h2>
      </div>
    </template>
    
    <script>
    import bus from './eventBus.js'
    
    export default {
      name: 'MyRight',
      data() {
        return {
          num: 0,
        }
      },
      created() {
        bus.on('countChange', count => {
          this.num = count
        })
      }
    }
    </script> 
    

效果:

Untitled



后代关系组件之间的数据共享-provide&inject

作用:跨层级共享数据

场景

Untitled

后代关系组件之间共享数据,指的是父节点的组件向其子孙组件共享数据。此时组件之间的嵌套关系比较复杂,可以使用 provide 和 inject 实现后代关系组件之间的数据共享。

语法

1-父组件 provide提供数据

export default {
  provide () {
    return {
       // 普通类型【非响应式】
       color: this.color, 
       // 复杂类型【响应式】
       userInfo: this.userInfo, 
    }
  }
}

2-子/孙组件 inject 获取数据

export default {
  inject: ['color','userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  }
}

图示

Untitled

注意:

1-provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)如上图所示,如果我用button修改color,那么图中元素不会有变动,而用button修改userInfo中的数据,则图中相应元素会有变动。

2-子/孙组件通过inject获取的数据,不能在自身组件内修改


补充:2021版的Vue课程

父节点对外共享响应式的数据

值得注意的是,provide中return回去的数据,并非是响应式的数据,即若我在父组件中用button修改p标签的颜色,子组件的中的p标签颜色不会跟着一块变。

父节点使用 provide 向下共享数据时,可以结合 computed 函数向下共享响应式的数据。示例代码如下:

Untitled

子孙节点使用响应式的数据

如果父级节点共享的是响应式的数据,则子孙节点必须以 .value 的形式进行使用。示例代码如下:

Untitled



vuex

vuex 是终极的组件之间的数据共享方案。在企业级的 vue 项目开发中,vuex 可以让组件之间的数据共享变得高效、清晰、且易于维护。


个人总结:

父子关系

① 父 -> 子 属性绑定
② 子 -> 父 事件绑定
③ 父 <-> 子 组件上的 v-model

兄弟关系

④ EventBus

后代关系

⑤ provide & inject

全局数据共享

⑥ vuex




组件的 props

为了提高组件的复用性,在封装 vue 组件时需要遵守如下的原则:

组件的 DOM 结构、Style 样式 要尽量复用

组件中要展示的数据,尽量由组件的使用者提供

为了方便使用者为组件提供要展示的数据,vue 组件提供了 props 的概念。



基础概念:

概念:组件上 注册的一些 自定义属性

作用:父组件通过 props 向子组件传递要展示的数据

特点:可以传递 任意数量与类型 的prop ;提高了组件的复用性

语法:简易写法

子组件接收

props: ['数据1', '数据2']

举例

传递父组件中的数据到子组件中

使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值

Untitled

注意: :username=”username” 左边是子,右边是父

效果:

Untitled

  • Code:

    App.vue

    <template>
      <div class="app">
        <UserInfo
          :username="username"
          :age="age"
          :isSingle="isSingle"
          :car="car"
          :hobby="hobby"
        ></UserInfo>
      </div>
    </template>
    
    <script>
    import UserInfo from './components/UserInfo.vue'
    export default {
      data() {
        return {
          username: '小帅',
          age: 28,
          isSingle: true,
          car: {
            brand: '宝马',
          },
          hobby: ['篮球', '足球', '羽毛球'],
        }
      },
      components: {
        UserInfo,
      },
    }
    </script>
    
    <style>
    </style>
    

    UserInfo.vue

    <template>
      <div class="userinfo">
        <h3>我是个人信息组件</h3>
        <div>姓名:{{ username }}</div>
        <div>年龄:{{ age }}</div>
        <div>是否单身:{{ isSingle }}</div>
        <div>座驾:{{ car.brand }}</div>
        <div>兴趣爱好 {{ hobby.join('、') }}</div>
      </div>
    </template>
    
    <script>
    export default {
      props: ['username', 'age', 'isSingle', 'car', 'hobby']
    }
    </script>
    
    <style>
    .userinfo {
      width: 300px;
      border: 3px solid #000;
      padding: 20px;
    }
    .userinfo > div {
      margin: 20px 10px;
    }
    </style>
    


props校验

作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误

语法

  • 类型校验(最常用)
props: {
	校验的属性名:类型    // Number String Boolean ...
}
  • 非空校验
  • 默认值
  • 自定义校验

举例:进度条的进度只能传入数字而不能是其他的数据类型

Untitled

BaseProgress.vue 子组件接收父组件的数据

export default {
  // 1.基础写法(类型校验)
  props: {
    w: Number,
  },
}

props校验完整写法

类型校验是最常用的,如果你需要后面几种校验,就需要补充以下的写法:

语法

props: {
  校验的属性名: {
    type: 类型,  // Number String Boolean ...
    required: true, // 是否必填
    default: 默认值, // 默认值
    validator (value) {
      // 自定义校验逻辑
      return 是否通过校验
    }
  }
},

代码示例:

<script>
export default {
  // 完整写法(类型、默认值、非空、自定义校验)
  props: {
    w: {
      type: Number,
      //required: true, 
      default: 0,
      validator(val) {
        // console.log(val)
        if (val >= 100 || val <= 0) {
          console.error('传入的范围必须是0-100之间')
          return false
        } else {
          return true
        }
      },
    },
  },
}
</script>

注意

1.default和required一般不同时写(因为当时必填项时,肯定是有值的)

2.default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值


props&data、单向数据流

**共同点:**都可以给组件提供数据

区别:

  • data 的数据是自己的 → 随便改
  • prop 的数据是外部的 → 不能直接改,要遵循 单向数据流

单向数据流:

父级props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的,即父的数据更新流向子

子若想影响父的数据,需要通过$.emit来影响父。然后,父在将数据单向流动给子。

Untitled

口诀:谁的数据谁负责

案例:子接收父Count值,并且子通过 this.$emit 传递方法给父,让其修改数据

Untitled

子想要改变父数据,需要通过 this.$emit 进行传递数据

父接收子changeCount方法,并利用 handleChange 接收数据,从而修改自身Count值

Untitled

注意:@changeCount=”handleChange” 左边是子,右边是父

  • Code:

    App.vue

    <template>
      <div class="app">
        <BaseCount
          @changeCount="handleChange"
          :count="count"
        ></BaseCount>
      </div>
    </template>
    
    <script>
    import BaseCount from './components/BaseCount.vue'
    export default {
      components:{
        BaseCount
      },
      data(){
        return {
          count:100
        }
      },
      methods:{
        handleChange(newCount) {
          this.count = newCount
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    

    BaseCount.vue

    <template>
      <div class="base-count">
        <button @click="handleSub()">-</button>
        <span>{{ count }}</span>
        <button @click="handleAdd()">+</button>
      </div>
    </template>
    
    <script>
    export default {
      // 1.自己的数据随便修改  (谁的数据 谁负责)
      // data () {
      //   return {
      //     count: 100,
      //   }
      // },
      // 2.外部传过来的数据 不能随便修改
      props: {
        count: Number,
      },
      methods: {
        handleAdd() {
          this.$emit('changeCount', this.count + 1)
        },
        handleSub() {
          this.$emit('changeCount', this.count - 1)
        }
      }
    }
    </script>
    
    <style>
    .base-count {
      margin: 20px;
    }
    </style>
    


props 的大小写命名

组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,

则有两种方式为其绑定属性的值:

Untitled

理解:

封装的时候采用驼峰命名法,那么外界在传递属性的时候既可以通过短横线命名,也可以通过驼峰命名法命名

注意:

如果我们在组件命名属性时采用驼峰命名法,

<script>
export default {
  name: 'MyArticle',
  // 外界可以传递指定的数据,到当前的组件中
  props: ['author', 'title', 'MyTest']
}
</script>

那么,在传递属性时,我们既可以使用驼峰命名法,也可以使用短横线命名法

<my-article :title="info.title" :author="info.author" :MyTest="info.MyTest"></my-article>



案例:小黑记事本-组件版

案例效果

Untitled

需求说明:

  • 拆分基础组件
  • 渲染待办任务
  • 添加任务
  • 删除任务
  • 底部合计 和 清空功能
  • 持久化存储

拆分基础组件:

咱们可以把小黑记事本原有的结构拆成三部分内容:头部(TodoHeader)、列表(TodoMain)、底部(TodoFooter)

思路:

Untitled

具体操作


1-拆分并渲染

/**
 * 渲染功能:
 * 1. 子组件提供数据给父组件
 * 2. 父传数据给子
 * 3. 利用 v-for 渲染数据
 */
  • Code:

    App.vue

    <template>
      <!-- 主体区域 -->
      <section id="app">
        <TodoHeader></TodoHeader>
        <TodoMain :list="list"></TodoMain>
        <TodoFooter></TodoFooter>
      </section>
    </template>
    
    <script>
    import TodoHeader from './components/TodoHeader.vue'
    import TodoMain from './components/TodoMain.vue'
    import TodoFooter from './components/TodoFooter.vue'
    /**
     * 渲染功能:
     * 1. 子组件提供数据给父组件
     * 2. 父传数据给子
     * 3. 利用 v-for 渲染数据
     */
    
    export default {
      components: {
        TodoHeader,
        TodoMain,
        TodoFooter,
      },
      data () {
        return {
          list: [
            { id: 1, name: '打篮球1'},
            { id: 2, name: '打篮球1'},
            { id: 3, name: '打篮球1'}
          ]
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    

    TodoMain.vue

    <template>
      <div>
          <!-- 列表区域 -->
          <section class="main">
          <ul class="todo-list">
            <li class="todo" v-for="(item, index) in list" :key="item.id">
              <div class="view">
                <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
                <button class="destroy" ></button>
              </div>
            </li>
    
          </ul>
        </section>
      </div>
    </template>
    
    <script>
    export default {
      name: 'TodoMain',
      data() {
        return {
          
        }
      },
      props: {
        list: Array
      },
      methods: {
    
      },
    }
    </script>
    
    <style>
    
    </style>
    

2-添加功能(添加、删除、统计、清空、持久化存储)

/**
 * 添加功能:
 * 1. 收集表单数据 v-model
 * 2. 监听事件(回车+点击 都要进行添加)
 * 3. 子传父,将任务名称传递给父组件
 * 4. 父组件进行添加 unshift(自己的数据自己负责)
 */
/**
 * 删除功能:
 * 1. 监听时间(监听删除的点击)携带id
 * 2. 子传父,将删除的id传递给父组件App.vue
 * 3. 进行删除 filter (自己的数据自己负责)
 */
// 底部合计:父组件传递list到底部组件  —>展示合计
// 清空功能:监听事件 —> **子组件**通知父组件 —>父组件清空
// 持久化存储: watch监听数据变化,持久化到本地
  • Code:

    App.vue

    <template>
      <!-- 主体区域 -->
      <section id="app">
        <TodoHeader @add="handleAdd"></TodoHeader>
        <TodoMain :list="list" @del="handleDel"></TodoMain>
        <TodoFooter :total="this.list.length" @clear="handleClear"></TodoFooter>
      </section>
    </template>
    
    <script>
    import TodoHeader from './components/TodoHeader.vue'
    import TodoMain from './components/TodoMain.vue'
    import TodoFooter from './components/TodoFooter.vue'
    /**
     * 渲染功能:
     * 1. 子组件提供数据给父组件
     * 2. 父传数据给子
     * 3. 利用 v-for 渲染数据
     */
    /**
     * 添加功能:
     * 1. 收集表单数据 v-model
     * 2. 监听事件(回车+点击 都要进行添加)
     * 3. 子传父,将任务名称传递给父组件
     * 4. 父组件进行添加 unshift(自己的数据自己负责)
     */
    /**
     * 删除功能:
     * 1. 监听时间(监听删除的点击)携带id
     * 2. 子传父,将删除的id传递给父组件App.vue
     * 3. 进行删除 filter (自己的数据自己负责)
     */
    /**
     * 持久化存储: watch监听数据变化,持久化到本地
     */
    const defaultList = [
      { id: 1, name: '打篮球1'},
      { id: 2, name: '打篮球1'},
      { id: 3, name: '打篮球1'}
    ]
    export default {
      components: {
        TodoHeader,
        TodoMain,
        TodoFooter,
      },
      data () {
        return {
          list: JSON.parse(localStorage.getItem('list')) || defaultList,
        }
      },
      methods: {
        handleAdd(todoName) {
          this.list.unshift({
            id: +new Date(),
            name: todoName,
          })
        },
        handleDel(id) {
          // console.log(id);
          this.list = this.list.filter(item => item.id !== id)
        },
        handleClear() {
          this.list = []
        }
      },
      watch: {
        list: {
          deep: true,
          handler(newValue) {
            localStorage.setItem('list', JSON.stringify(newValue))
          }
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    

    TodoHeader.vue

    <template>
      <div>
        <!-- 输入框 -->
        <header class="header">
          <h1>小黑记事本</h1>
          <input placeholder="请输入任务" class="new-todo" v-model="todoName" @keyup.enter="handleAdd"/>
          <button class="add" @click="handleAdd" >添加任务</button>
        </header>
      </div>
    </template>
    
    <script>
    export default {
      name: 'TodoHeader',
      data() {
        return {
          todoName: '',
        }
      },
      methods: {
        handleAdd() {
          if(this.todoName.trim() === '') {
            alert("请输入内容!")
            return
          }
          this.$emit('add', this.todoName)
          this.todoName = ''
        },
      },
    }
    </script>
    
    <style>
    
    </style>
    

    TodoMain.vue

    <template>
      <div>
          <!-- 列表区域 -->
          <section class="main">
          <ul class="todo-list">
            <li class="todo" v-for="(item, index) in list" :key="item.id">
              <div class="view">
                <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
                <button class="destroy" @click="handleDel(item.id)"></button>
              </div>
            </li>
          </ul>
        </section>
      </div>
    </template>
    
    <script>
    export default {
      name: 'TodoMain',
      data() {
        return {
          
        }
      },
      props: { 
        list: Array
      },
      methods: {
        handleDel(id) {
          // console.log(id);
          this.$emit("del", id)
        }
      },
    }
    </script>
    
    <style>
    
    </style>
    

    TodoFooter.vue

    <template>
      <div>
        <!-- 统计和清空 -->
        <footer class="footer">
          <!-- 统计 -->
          <span class="todo-count">合 计:<strong> {{ total }} </strong></span>
          <!-- 清空 -->
          <button class="clear-completed" @click="clear">
            清空任务
          </button>
        </footer>
      </div>
    </template>
    
    <script>
    export default {
      name: 'TodoFooter',
      data() {
        return {
          
        }
      },
      props: {
        total: Number,
      },
      methods: {
        clear() {
          this.$emit('clear')
        }
      },
    
    }
    </script>
    
    <style>
    
    </style>
    



v-model 原理

基本原理

**原理:**v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写

结合这段代码理解:

Untitled

<template>
  <div id="app" >
    <input v-model="msg" type="text">

    <input :value="msg" @input="msg = $event.target.value" type="text">
  </div>
</template>

注意:$event 用于在模板中,获取事件的形参

用:v-model提供数据的双向绑定

  • 数据变,视图跟着变 :value
  • 视图变,数据跟着变 @input

代码实例:两个input是同步进退的

Untitled

<template>
  <div class="app">
    <input type="text" v-model="msg1"/>
    <br />
    <input type="text" :value="msg1" @input="msg=$event.target.value">
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg1: '',
    }
  },
}
</script>

<style>
</style>

v-model使用在其他表单元素上的原理

不同的表单元素, v-model在底层的处理机制是不一样的。比如给checkbox使用v-model底层处理的是 checked属性和change事件。

不过咱们只需要掌握应用在文本框上的原理即可



表单类组件封装

目标:实现子组件和父组件数据的双向绑定

案例:实现App.vue中的selectId和子组件选中的数据进行双向绑定

Untitled

App.vue

<template>
  <div class="app">
    <BaseSelect
      :cityId = "selectId"
      @changeId="selectId = $event" //用 $event 表示当前形参
    ></BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '104',
    }
  },
  components: {
    BaseSelect,
  },
}
</script>

<style>
</style>

BaseSelect.vue

<template>
  <div>
    <select :value="cityId" @change="handleChange">
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>
export default {
  props: {
    cityId: String,
  },
  methods: {
    // e 指触发事件的事件源
    handleChange(e) {
      console.log(e.target.value);
      this.$emit('changeId', e.target.value)
    }
  }
}
</script>

<style>
</style>


v-model 简化代码

**目标:**父组件通过v-model 简化代码,实现子组件和父组件数据 双向绑定

简化:

v-model其实就是 :value和@input事件的简写

  • 子组件:props通过value接收数据,事件触发 input
  • 父组件:v-model直接绑定数据

案例

子组件

<select :value="value" @change="handleChange">...</select>
props: {
  value: String
},
methods: {
  handleChange (e) {
		// 将这里的handleChange改成input
    this.$emit('input', e.target.value)
  }
}

父组件

<BaseSelect v-model="selectId"></BaseSelect>

总结

Untitled

可以结合上面的 父子组件之间数据 的双向同步



.sync修饰符(建议对比理解前几个双向绑定)

作用:可以实现 子组件父组件数据双向绑定,简化代码

简单理解:子组件可以修改父组件传过来的props值

特点:prop属性名,可以自定义,非固定为 value (这与v-model不同)

场景: 封装弹框类的基础组件, visible属性 true显示 false隐藏

理解:如果封装的不是value,而是这种弹框类的组件,建议用.sync建立双向绑定

Untitled

本质: .sync修饰符 就是 :属性名@update:属性名 合写

语法

父组件

//.sync写法
<BaseDialog :visible.sync="isShow" />
--------------------------------------
//完整写法
<BaseDialog
  :visible="isShow"
  @update:visible="isShow = $event"
/>

子组件

props: {
  visible: Boolean
},

this.$emit('update:visible', false)

案例

Untitled

  • Code:

    App.vue

    <template>
      <div class="app">
        <button
          @click="isShow = true"
        >退出按钮</button>
        <BaseDialog
          :visible.sync="isShow"
        ></BaseDialog>
      </div>
    </template>
    
    <script>
    import BaseDialog from "./components/BaseDialog.vue"
    export default {
      data() {
        return {
          isShow: false
        }
      },
      methods: {
        
      },
      components: {
        BaseDialog,
      },
    }
    </script>
    
    <style>
    </style>
    

    BaseDialog.vue

    <template>
      <div class="base-dialog-wrap" v-show="visible">
        <div class="base-dialog">
          <div class="title">
            <h3>温馨提示:</h3>
            <button class="close" @click="close">x</button>
          </div>
          <div class="content">
            <p>你确认要退出本系统么?</p>
          </div>
          <div class="footer">
            <button>确认</button>
            <button>取消</button>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        visible: Boolean,
      },
      methods: {
        close() {
          this.$emit('update:visible', false)
        }
      }
    }
    </script>
    
    <style scoped>
    .base-dialog-wrap {
      width: 300px;
      height: 200px;
      box-shadow: 2px 2px 2px 2px #ccc;
      position: fixed;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      padding: 0 10px;
    }
    .base-dialog .title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 2px solid #000;
    }
    .base-dialog .content {
      margin-top: 38px;
    }
    .base-dialog .title .close {
      width: 20px;
      height: 20px;
      cursor: pointer;
      line-height: 10px;
    }
    .footer {
      display: flex;
      justify-content: flex-end;
      margin-top: 26px;
    }
    .footer button {
      width: 80px;
      height: 40px;
    }
    .footer button:nth-child(1) {
      margin-right: 10px;
      cursor: pointer;
    }
    </style>
    


ref和$refs

作用: 利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例

理解:每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

**特点:**查找范围 → 当前组件内(更精确稳定)

技术诞生原因

如果使用querySelector进行查找图标.box, 可能在整个页面找到多个.box。

因此,为了更加精准地获取DOM元素,就需要ref与$refs这种技术

Untitled

语法

1.给要获取的盒子添加ref属性

<div ref="chartRef">我是渲染图表的容器</div>

2.获取时通过 r e f s 获取 t h i s . refs获取 this. refs获取this.refs.chartRef 获取

mounted () {
  console.log(this.$refs.chartRef)
}

注意

之前只用document.querySelect(‘.box’) 获取的是整个页面中的盒子

案例-1:获取DOM元素

Untitled

  • Code:

    App.vue

    <template>
      <div class="app">
        <div class="base-chart-box">
          这是一个捣乱的盒子
        </div>
        <BaseChart></BaseChart>
      </div>
    </template>
    
    <script>
    import BaseChart from './components/BaseChart.vue'
    export default {
      components:{
        BaseChart
      }
    }
    </script>
    
    <style>
    .base-chart-box {
      width: 200px;
      height: 100px;
    }
    </style>
    

    BaseChart.vue

    <template>
      <div ref="mychart" class="base-chart-box">子组件</div>
    </template>
    
    <script>
    import * as echarts from 'echarts'
    
    export default {
      mounted() {
        // 基于准备好的dom,初始化echarts实例
        const myChart = echarts.init(this.$refs.mychart)
        // 绘制图表
        myChart.setOption({
          title: {
            text: 'ECharts 入门示例',
          },
          tooltip: {},
          xAxis: {
            data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
          },
          yAxis: {},
          series: [
            {
              name: '销量',
              type: 'bar',
              data: [5, 20, 36, 10, 10, 20],
            },
          ],
        })
      },
    }
    </script>
    
    <style scoped>
    .base-chart-box {
      width: 400px;
      height: 300px;
      border: 3px solid #000;
      border-radius: 6px;
    }
    </style>
    

案例-2:获取组件实例

Untitled

  • Code:

    App.vue

    <template>
      <div class="app">
        <h4>父组件 -- <button>获取组件实例</button></h4>
        <BaseForm ref="baseForm"></BaseForm>
    
        <button @click="handleGet">父-获取数据</button>
        <button @click="handleReset">父-重置数据</button>
      </div>
    </template>
    
    <script>
    import BaseForm from './components/BaseForm.vue'
    export default {
      components: {
        BaseForm,
      },
      methods: {
        handleGet() {
          this.$refs.baseForm.getFormData()
        },
        handleReset() {
          this.$refs.baseForm.resetFormData()
        }
      }
    }
    </script>
    
    <style>
    </style>
    

    BaseForm.vue

    <template>
      <div class="app">
        <div>
          账号: <input v-model="username" type="text">
        </div>
         <div>
          密码: <input v-model="password" type="text">
        </div>
        <div>
          <button @click="getFormData">获取数据</button>
          <button @click="resetFormData">重置数据</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          username: 'admin',
          password: '123456',
        }
      },
      methods: {
        getFormData() {
          console.log('获取表单数据', this.username, this.password);
        },
        resetFormData() {
          this.username = ''
          this.password = ''
          console.log('重置表单数据成功');
        },
      }
    }
    </script>
    
    <style scoped>
    .app {
      border: 2px solid #ccc;
      padding: 10px;
    }
    .app div{
      margin: 10px 0;
    }
    .app div button{
      margin-right: 8px;
    }
    </style>
    


异步更新 & $nextTick

需求:

编辑标题, 编辑框自动聚焦

  1. 点击编辑,显示编辑框
  2. 让编辑框,立刻获取焦点

Untitled

**代码实现:

<template>
  <div class="app">
    <div v-if="isShowEdit">
      <input type="text" v-model="editValue" ref="inp" />
      <button>确认</button>
    </div>
    <div v-else>
      <span>{{ title }}</span>
      <button @click="editFn">编辑</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '大标题',
      isShowEdit: false,
      editValue: '',
    }
  },
  methods: {
    editFn() {
        // 显示输入框
        this.isShowEdit = true  
        // 获取焦点
        this.$refs.inp.focus() 
    }  },
}
</script>

问题:

“显示之后”,立刻获取焦点是不能成功的!

原因:Vue 是异步更新DOM (提升性能)

解决方案

$nextTick:等 DOM更新后,才会触发执行此方法里的函数体

语法: this.$nextTick(函数体)

this.$nextTick(() => {
  this.$refs.inp.focus()
})

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

补充:用setTimeout也能实现,但是它的时间没有$nextTick精准

解决代码

  • Code:

    App.vue

    <template>
      <div class="app">
        <div v-if="isShowEdit">
          <input type="text" v-model="editValue" ref="inp" />
          <button>确认</button>
        </div>
        <div v-else>
          <span>{{ title }}</span>
          <button @click="handleEidt">编辑</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          title: '大标题',
          isShowEdit: false,
          editValue: '',
        }
      },
      methods: {
       handleEidt() {
        //1. 显示输入框
        this.isShowEdit = true
        //2. 让输入框获取焦点
        this.$nextTick(() => {
          this.$refs.inp.focus()
        })
       }
      },
    }
    </script>
    
    <style>
    </style>
    

    BaseForm.vue

    <template>
      <div class="app">
        <div>
          账号: <input v-model="username" type="text">
        </div>
         <div>
          密码: <input v-model="password" type="text">
        </div>
        <div>
          <button @click="getFormData">获取数据</button>
          <button @click="resetFormData">重置数据</button>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          username: 'admin',
          password: '123456',
        }
      },
      methods: {
        getFormData() {
          console.log('获取表单数据', this.username, this.password);
        },
        resetFormData() {
          this.username = ''
          this.password = ''
          console.log('重置表单数据成功');
        },
      }
    }
    </script>
    
    <style scoped>
    .app {
      border: 2px solid #ccc;
      padding: 10px;
    }
    .app div{
      margin: 10px 0;
    }
    .app div button{
      margin-right: 8px;
    }
    </style>
    

总结

Untitled




组件的生命周期

组件运行的过程:

Untitled

组件的生命周期指的是:组件从创建 -> 运行(渲染) -> 销毁的整个过程,强调的是一个时间段

监听组件的不同时刻的方式

vue 框架为组件内置了不同时刻的生命周期函数,生命周期函数会伴随着组件的运行而自动调用。

例如:

① 当组件在内存中被创建完毕之后,会自动调用 created 函数

② 当组件被成功的渲染到页面上之后,会自动调用 mounted 函数

③ 当组件被销毁完毕之后,会自动调用 unmounted 函数

案例:

  • Code:

    App.vue

    <template>
      <div>
        <h1>App 根组件</h1>
        <hr/>
        <life-cycle v-if="flag"></life-cycle>
        <button @click="flag = !flag">Toggle</button>
      </div>
    </template>
    
    <script>
    import LifeCycle from './LifeCycle.vue'
    export default {
      name: 'MyApp',
      components: {
        LifeCycle
      },
      data() {
        return {
          flag: false,
        }
      },
    
    }
    </script>
    
    <style>
    
    </style>
    

    LifeCycle.vue

    <template>
      <div>
        <h2>LifeCycle</h2>
      </div>
    </template>
    
    <script>
    export default {
      name: 'LifeCycle',
    
      created() {
        console.log('组件在内存中被创建完毕了');
      },
      mounted() {
        console.log('组件被成功渲染到页面上了');
      },
      unmounted() {
        console.log('组件被销毁完毕了');
      }
    }
    </script>
    
    <style>
    
    </style>
    

理解:代码中的created mounted() unmounted函数放到子组件LifeCycle中,当子组件创建完毕之后,会调用created函数,当组件被渲染到页面上后,会调用mounted函数,当组件被销毁完毕之后,会调用unmounted函数。

监听组件的更新的方式

当组件的 data 数据更新之后,vue 会自动重新渲染组件的 DOM 结构,从而保证 View 视图展示的数据和Model 数据源保持一致。

当组件被重新渲染完毕之后,会自动调用 updated 生命周期函数。

案例:

  • Code:

    <template>
      <div>
        <h2>LifeCycle</h2>
        <p>{{ count }}</p>
        <button @click="count += 1">+1</button>
      </div>
    </template>
    
    export default {
      name: 'LifeCycle',
      data() {
        return {
          count: 0,
        }
      },
      updated() {
        console.log('组件被重新渲染完毕了');
      },
    }
    

组件中主要的生命周期函数

Untitled

注意:在实际开发中,created 是最常用的生命周期函数!

组件中全部的生命周期函数

Untitled

疑问:为什么不在 beforeCreate 中发 ajax 请求初始数据

发起Ajax请求最好都在creat中

完整的生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:
https://www.vue3js.cn/docs/zh/guide/instance.html#生命周期图示




vue 3.x 中全局配置 axios

  1. 为什么要全局配置 axios

在实际项目开发中,几乎每个组件中都会用到 axios 发起数据请求。此时会遇到如下两个问题:

① 每个组件中都需要导入 axios(代码臃肿)

② 每次发请求都需要填写完整的请求路径(不利于后期的维护)

Untitled

2. 如何全局配置 axios

在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios,示例代码如下:

Untitled

  • Code:

    main.js

    const app = createApp(MyApp)
    axios.defaults.baseURL = 'https://www.escook.cn' //为axios配置请求的根路径
    //将axios挂载为app的全局自定义属性之后,
    //每个组件可以通过this直接访问到全局挂载的自定义属性
    app.config.globalProperties.$http = axios
    

    GetInfo:

    export default {
      name: 'GetInfo',
      methods: {
        async getInfo() {
          const { data: res } = await this.$http.get('/api/get', {
            params: {
              name: 'ls',
              age: 33,
            },
          })
    
          console.log(res)
        },
      },
    }
    

    PostInfo:

    export default {
      name: 'PostInfo',
      methods: {
        async postInfo() {
          const { data: res } = await this.$http.post('/api/post', { name: 'zs', age: 20 })
          console.log(res)
        },
      },
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/841643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于自组织竞争网络的患者癌症发病预测(matlab代码)

1.案例背景 1.1自组织竞争网络概述 前面案例中讲述的都是在训练过程中采用有导师监督学习方式的神经网络模型。这种学习方式在训练过程中,需要预先给网络提供期望输出,根据期望输出来调整网络的权重,使得实际输出和期望输出尽可能地接近。但是在很多情况下,在人们认知的过程中…

分布式规则引擎框架的设计

MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架&#xff0c;允许用户对多个活动进行分组和自动化。 过去几年&#xff0c;在开发MirAIe 物联网平台时&#xff0c;我们意识到需要一个可扩展、可扩展的规则引擎框架。规则引擎使您能够对各种操作进行分组、管理和自动化&…

git【潦草学习】

初始配置git 查询版本号 初次使用git前配置用户名与邮箱地址 git config --global user.name "your name" git config --global user.email "your email" git config -l 发现最后两行多出了用户名和邮箱&#xff0c;说明配置成功

对指针变量引用以及自定义类型引用的认识

#include <iostream> using namespace std; #include <iomanip>typedef int G[10]; //自定义类型void test(int* p); // void test_0(int &p); //表明p是一个int型变量的别名 void test_1(G &pG); //表明pG是一个G类型变量的别名 void test_2(int*…

PMP备考心得分享

备考PMP考试是一段充满挑战和成长的旅程。参加某机构的PMP培训课程&#xff0c;有国内PMP考培资深讲师的授课&#xff0c;以及班主任的周期监督管理&#xff0c;无疑是我备考成功的重要支撑。在这个过程中&#xff0c;我积累了许多宝贵的经验和心得&#xff0c;现在将它们分享给…

GPT-5出世,需5万张H100!英伟达GPU陷短缺风暴

随着人工智能技术的不断突破&#xff0c;自然语言处理领域也掀起了一波又一波的革命。从GPT-3.5的惊艳登场&#xff0c;到紧随其后的GPT-4的惊世震人&#xff0c;人们仿佛置身于科幻电影中&#xff0c;亲历了一场场技术的奇迹。然而&#xff0c;这场奇迹背后却逐渐显现出一道不…

解决问题:ModuleNotFoundError: No module named ‘mmcv._ext‘,及安装mmcv-full的详细教程

解决问题**ModuleNotFoundError: No module named ‘mmcv._ext’**之前得先搞懂mmcv和mmcv-full的关系。 mmcv 和 mmcv-full 都是针对 PyTorch 的计算机视觉基础库,两者的主要区别是: mmcv 包含了 mmcv 的核心组件,例如运行器、回调函数、可视化工具等,打包体积较小。mmcv-fu…

K8S系列文章之 自动化运维利器 Fabric

Fabric 主要用在应用部署与系统管理等任务的自动化&#xff0c;简单轻量级&#xff0c;提供有丰富的 SSH 扩展接口。在 Fabric 1.x 版本中&#xff0c;它混杂了本地及远程两类功能&#xff1b;但自 Fabric 2.x 版本起&#xff0c;它分离出了独立的 Invoke 库&#xff0c;来处理…

bigemap国土管理行业应用

由于国营企业单位&#xff0c;管理土地&#xff0c;必须要有这样的软件套图 客户之前用的谷歌&#xff0c;后来不能访问了&#xff0c;通过其他途径搜索到我们 客户使用软件一般都用于套坐标以及空间规划图&#xff0c;方便于项目选址和居民建房报建在卫星图上找到用地范围&am…

远程调试MySQL内核

1 vscode 需要安装remote-ssh插件 安装成功后&#xff0c;登录&#xff1a; 默认远程服务器的登录 ssh rootip注意&#xff0c;Linux需要设置root远程登录&#xff1b; 2 安装debug扩展 C\C extemsion Pack C\Cgdb debugger beyond用于进程attach 3 设置Attach进程 {// …

MyBatis源码剖析之Mapper代理方式细节

MyBatis是一个流行的Java持久层框架&#xff0c;它提供了多种方式来执行数据库操作&#xff0c;其中之一就是通过Mapper代理方式。通过Mapper代理方式&#xff0c;开发者可以编写接口&#xff0c;然后MyBatis会动态地生成接口的实现类&#xff0c;从而避免了繁琐的SQL映射配置。…

前端主题切换方案——CSS变量

前言 主题切换是前端开发中老生常谈的问题&#xff0c;本文将介绍主流的前端主题切换实现方案——CSS变量 CSS变量 简介 编写CSS样式时&#xff0c;为了避免代码冗余&#xff0c;降低维护成本&#xff0c;一些CSS预编译工具&#xff08;Sass/Less/Stylus&#xff09;等都支…

Java基础入门篇——Java注释、关键字和标识符(二)

目录 一、注释 1.1注释介绍 1.2单行注释 1.3多行注释 1.4文档注释 1.5javadoc注释标签语法 二、关键字 三、字面量 3.1什么是标识符&#xff1f; 3.2标识符命名规则 四、总结 一、注释 在Java中&#xff0c;注释是用来向代码添加解释、说明和文档信息的文本。Java…

网工这行最大的内卷,就是养了一批“HCIE”

下午好&#xff0c;我是老杨。 这段时间&#xff0c;我个人工作上的事儿挺多&#xff0c;一直在处理。 手底下的一个重要项目组&#xff0c;想招个新人&#xff0c;面了几批都不满意&#xff0c;难搞。 前两天刚定下人选&#xff0c;现下和你聊上几句。 上回说过&#xff0…

Killing LeetCode [82] 删除排序链表中的重复元素 II

Description 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 Intro Ref Link&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/ Difficulty&#xff1a;Medium T…

怎么在树莓派环境上搭建web网站,并发布到外网可访问,今天教给大家

怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f; 文章目录 怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f;概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpo…

OpenStack监控工具

OpenStack是一个开源的云计算管理平台项目&#xff0c;是一系列软件开源项目的组合。由NASA和Rackspace合作研发并发起&#xff0c;以Apache许可证&#xff08;Apache软件基金会发布的一个自由软件许可证&#xff09;授权。 OpenStack为私有云和公有云提供可扩展的弹性的云计算…

mac电脑三维建模 Rhino 7中文 7.32

Rhino 7特别功能介绍 • 不受约束的自由形式 3D 建模工具&#xff0c;如仅在成本高出 20 到 50 倍的产品中发现的工具。为您可以想象的任何形状建模。 • 设计、原型、工程、分析和制造从飞机到珠宝的任何东西所需的准确性。 • 与您的所有其他设计、绘图、CAM、工程、分析、…

git使用(常见用法)

一.下载git git官方下载跳转 安装简单,有手就行 二. git的简单使用 1. 连接远程仓库 #初始化 git init #配置账户 git config --global user.name “输入你的用户名” git config --global user.email “输入你的邮箱” git config --list #--q退出 #配置验证邮箱 ssh-key…

vue2 引用swiper

参考引用 &#xff1a; vue使用swiper实现轮播图一页多张图片效果_swiper多张图片排列的轮播_空空的博客-CSDN博客 vue2 使用swiper_vue2使用swiper__NIXIAKF的博客-CSDN博客 常见的属性&#xff1a;Swiper 中常见的属性以及方法_swiper-wrapper_超勇的.的博客-CSDN博客 1、…