vue3基础知识

一、jsx 结构

1. 结构代码示例

  • setup() 返回一个渲染函数
  • 渲染函数返回 JSX 结构
  • JSX 最终被编译为 h() 函数调用 index.jsx
javascript 复制代码
export default {
  setup() {
    // 返回一个函数,该函数返回JSX元素
    return () => <div>123</div>;
  }
};

2. setup 说明

setup() 关键特性解析:

1. 渲染函数模式

  • setup() 返回的是一个函数 而不是对象

  • 这个返回的函数就是组件的 渲染函数

  • 当组件需要渲染时,Vue 会自动调用这个函数
    2. JSX 语法

  • 使用 JSX 语法 (<div>123</div>) 描述 UI

  • 相当于 Vue 的 h() 函数 :h('div', {}, '123')

  • 需要 Babel 插件(如 @vue/babel-plugin-jsx)支持
    3. 与常规 setup 的区别

常规 setup 本例 setup
返回数据对象给模板使用 直接返回渲染函数
需要单独的 <template> 部分 渲染逻辑全在 JS 中
适合简单组件 适合需要灵活控制渲染的场景

4. 实际等效代码

javascript 复制代码
import { h } from 'vue';

export default {
 setup() {
   // 使用 h 函数实现相同效果
   return () => h('div', {}, '123');
 }
}

3. h 函数

在 Vue 中,h 函数是创建虚拟 DOM 节点(VNode)的核心函数。它的名字来源于"hyperscript",意为"生成 HTML 结构的脚本"。让我们深入理解它的作用和用法:

核心概念

1. 虚拟 DOM (Virtual DOM) :

  • JavaScript 对象表示的真实 DOM 的轻量级副本
  • Vue 通过比较新旧 VNode 来高效更新真实 DOM
  • 避免直接操作真实 DOM,提高性能

2. h 函数的作用:

  • 创建描述 DOM 节点的 JavaScript 对象(VNode)
  • 接收参数定义节点类型、属性和子元素
  • JSX 语法最终会被编译为 h 函数调用

函数签名

typescript 复制代码
function h(
  type: string | Component,  // HTML 标签名或 Vue 组件
  props?: object,            // 属性/事件对象
  children?: string | Array<VNode> // 文本内容或子 VNode
): VNode

使用示例

场景 JSX 写法 h 函数等价写法
文本元素 <div>Hello</div> h('div', 'Hello')
带属性的元素 <div class="box"></div> h('div', { class: 'box' })
嵌套元素 <div><span>Hi</span></div> h('div', [h('span', 'Hi')])
组件使用 <MyComponent prop1="value" /> h(MyComponent, { prop1: 'value' })

在 Vue 3 中的特殊作用

less 复制代码
// JSX
<button onClick={handler}>Click</button>

// 编译后
h('button', { onClick: handler }, 'Click')

为什么需要 h 函数?

  • 跨平台能力:相同的 VNode 可在不同环境渲染(Web、Canvas、Native)
  • 渲染优化:通过 diff 算法最小化 DOM 操作
  • 灵活组合:可用纯 JavaScript 表达任意 UI 结构
  • 类型安全:在 TypeScript 中提供更好的类型支持

二、常用指令

1. v-model

javascript 复制代码
import { reactive } from "vue"

export default {
  setup() {
    // 创建响应式状态对象, 
    const state = reactive({
      msg: 'hello',
    })
    
    // 返回渲染函数(JSX)
    return () =>
      <div>
        {/* `state.msg` 是响应式属性,值变化会自动更新 UI */}
        <el-input v-model={state.msg} />
      </div>
  }
}

双向数据绑定原理

ini 复制代码
<el-input
  modelValue={state.msg}
  onUpdate:modelValue={(value) => state.msg = value}
/>

相当于:

  1. state.msg 作为初始值传递给输入框
  2. 当输入框值变化时,通过事件更新 state.msg
  3. state.msg 变化触发重新渲染

2. v-if

使用逻辑与 (&&) 运算符

javascript 复制代码
import {  reactive } from "vue"
export default {

  setup () {
    const state = reactive({
      condition: true,
    })
    return () => <>
    {/* 使用逻辑与 (&&) 运算符 */}
     {state.condition && <div>条件为真时显示</div>}
    </>
  }
}

使用三元表达式

javascript 复制代码
import { reactive } from "vue"
export default {

  setup () {
    const state = reactive({
      condition: false,
    })
    return () => <>
      {state.condition
        ? <div>条件为真时显示</div>
        : <div>条件为假时显示</div>
      }
    </>
  }
}

使用函数

javascript 复制代码
import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      condition: false,
      anotherCondition: true,
    });

    const renderContent = () => {
      if (state.condition) {
        return <div>复杂条件分支1</div>;
      } else if (state.anotherCondition) {
        return <div>复杂条件分支2</div>;
      } else {
        return <div>默认内容</div>;
      }
    };

    return () => (
      <>
        {renderContent()}
      </>
    );
  }
};

3. v-show

javascript 复制代码
import { reactive } from "vue";

export default {
  setup() {
    const state = reactive({
      condition: true,
    });

    return () => (
      <>
        <div v-show={state.condition}>233333354</div>
      </>
    );
  }
};

4. v-for

在 Vue 3 的 JSX 中,没有直接的 v-for 指令,但我们可以使用 JavaScript 的数组方法(如 map())来实现相同的功能。下面我将详细介绍如何在 JSX 中实现 v-for 功能,并提供完整的示例代码。

v-for 在 JSX 中的实现原理

v-for 的本质是遍历数组或对象并渲染多个元素。在 JSX 中,我们使用 map() 方法来实现:

索引值的使用

php 复制代码
import { reactive } from "vue";

export default {
  setup () {
   const items = reactive([
      { id: 1, name: 'Apple', price: 2.5, category: 'fruit' },
      { id: 2, name: 'Banana', price: 1.2, category: 'fruit' },
      { id: 3, name: 'Carrot', price: 0.8, category: 'vegetable' },
      { id: 4, name: 'Milk', price: 2.0, category: 'dairy' },
      { id: 5, name: 'Bread', price: 1.5, category: 'bakery' },
    ]);

    return () => (
      <>
        {items.map((item, index) => (
          <div key={item.id} class="item">
            <span class="index">{index + 1}.</span>
            <span class="name">{item.name}</span>
          </div>
        ))}
      </>
    );
  }
};

嵌套循环

php 复制代码
import { reactive } from "vue";

export default {
  setup () {
    const categories = reactive([
      {
        id: 1,
        name: "Fruits",
        products: [
          { id: 1, name: "Apple" },
          { id: 2, name: "Banana" },
          { id: 3, name: "Orange" }
        ]
      },
      {
        id: 2,
        name: "Vegetables",
        products: [
          { id: 4, name: "Carrot" },
          { id: 5, name: "Broccoli" },
          { id: 6, name: "Tomato" }
        ]
      },
      {
        id: 3,
        name: "Dairy",
        products: [
          { id: 7, name: "Milk" },
          { id: 8, name: "Cheese" },
          { id: 9, name: "Yogurt" }
        ]
      }
    ]);

    return () => (
      <>
        {categories.map(category => (
          <div key={category.id}>
            <h3>{category.name}</h3>
            {category.products.map(product => (
              <div key={product.id} class="product">
                {product.name}
              </div>
            ))}
          </div>
        ))}
      </>
    );
  }
};

遍历对象

javascript 复制代码
import { reactive } from "vue";

export default {
  setup () {
    const user = reactive({
      name: "John Doe",
      age: 30,
      email: "johndoe@example.com",
      address: "123 Main St, Anytown, USA",
      phone: "555-555-1234"
    });

    return () => (
      <>
        {Object.entries(user).map(([key, value]) => (
          <div key={key} class="property">
            <span class="key">{key}:</span>
            <span class="value">{value}</span>
          </div>
        ))}
      </>
    );
  }
};

范围迭代(模拟 v-for="n in 10")

javascript 复制代码
export default {
  setup () {

    return () => (
      <>
        {Array.from({ length: 10 }).map((_, index) => (
          <div key={index} class="item">
            项目 {index + 1}
          </div>
        ))}
      </>
    );
  }
};

5. v-for 与 v-if 结合使用

推荐做法:先过滤再渲染

php 复制代码
import { reactive } from "vue";

export default {
  setup () {
    const items = reactive([
      { id: 1, name: 'Apple', price: 2.5, category: 'fruit' },
      { id: 2, name: 'Banana', price: 1.2, category: 'fruit' },
      { id: 3, name: 'Carrot', price: 0.8, category: 'vegetable' },
      { id: 4, name: 'Milk', price: 2.0, category: 'dairy' },
      { id: 5, name: 'Bread', price: 1.5, category: 'bakery' },
    ]);

    return () => (
      <>
        {items
          .filter(item => item.price > 1)
          .map(item => (
            <div key={item.id}>{item.name}</div>
          ))
        }
      </>
    );
  }
};

复杂场景:使用条件渲染

php 复制代码
import { reactive } from "vue";

export default {
  setup () {
    const items = reactive([
      { id: 1, name: 'Apple', price: 2.5, category: 'fruit' },
      { id: 2, name: 'Banana', price: 1.2, category: 'fruit' },
      { id: 3, name: 'Carrot', price: 0.8, category: 'vegetable' },
      { id: 4, name: 'Milk', price: 2.0, category: 'dairy' },
      { id: 5, name: 'Bread', price: 1.5, category: 'bakery' },
    ]);

    return () => (
      <>
        {items.map(item => (
          item.price > 1 ? (
            <div key={item.id} class="expensive">
              {item.name} (${item.price})
            </div>
          ) : (
            <div key={item.id} class="cheap">
              {item.name} (${item.price})
            </div>
          )
        ))}
      </>
    );
  }
};

6. v-for 性能优化技巧

1. 使用唯一且稳定的 key

// 好 - 使用唯一ID

<div key={item.id}>...</div>

// 避免 - 使用索引

<div key={index}>...</div>

2. 虚拟滚动优化长列表

javascript 复制代码
import { VirtualList } from 'vue-virtual-scroller';

<VirtualList
 items={largeList.value}
 itemSize={50}
>
 {item => (
  <div key={item.id} class="item">
     {item.name}
   </div>
 )}
</VirtualList>

3. 提取子组件

javascript 复制代码
// ItemComponent.jsx
export default defineComponent({
 props: ['item'],
 setup(props) {
   return () => (
     <div class="item">
       <h4>{props.item.name}</h4>
       <p>${props.item.price}</p>
     </div>
   );
 }
});

// 在父组件中使用
{items.value.map(item => (
 <ItemComponent key={item.id} item={item} />
))}

与模板语法对比

模板语法 JSX 实现
<div v-for="item in items" :key="item.id"> {items.map(item => <div key={item.id}>)}
<div v-for="(item, index) in items"> {items.map((item, index) => <div key={item.id}>)}
<div v-for="(value, key) in object"> {Object.entries(object).map(([key, value]) => <div>)}
<div v-for="n in 10"> {Array.from({length: 10}).map((_, n) => <div key={n}>)}

三、事件

1. 父子组件事件

注意: 父组件需要 on +子组件 emit 上报的方法

ini 复制代码
// 父组件:onChangePageTitle 
<Header title={state.msg} onChangePageTitle={onChangePageTitle}/>

// 子组件: changePageTitle上报
 <el-button onClick={() => emit('changePageTitle', '用户页面')}>子组件事件上报</el-button>

四、父子组件

1. 子组件

接收数据

  1. setup (props, { emit, slots }) 接收 props
  2. 书写 props 对象
dart 复制代码
export default {
 name: 'ChildComponent',

 // 定义组件的 props
 props: {
  // message prop,类型为 String,默认值为 '默认消息'
   message: {
     type: String,
    default: '默认消息'
  },
  // items prop,类型为 Array,默认值为空数组
  items: {
    type: Array,
     default: () => []
   }
 },

    // setup 函数是 Vue 3 Composition API 的入口
    setup (props, { emit, slots }) {
  
    }
};
javascript 复制代码
import { ref } from 'vue';

export default {
  name: 'ChildComponent',

  // 定义组件的 props
  props: {
    // message prop,类型为 String,默认值为 '默认消息'
    message: {
      type: String,
      default: '默认消息'
    },
    // items prop,类型为 Array,默认值为空数组
    items: {
      type: Array,
      default: () => []
    }
  },
  // 声明组件可以发出的事件
  emits: ['childEvent'], // 声明自定义事件

  // setup 函数是 Vue 3 Composition API 的入口
  setup (props, { emit, slots }) {
    // 定义子组件内部状态
    const childMessage = ref('子组件内部状态');
    const inputValue = ref('');

    // 定义一个方法,用于向父组件发送数据
    function sendToParent () {
      emit('childEvent', {
        childMessage: childMessage.value,
        inputValue: inputValue.value,
        timestamp: Date.now()
      })
    }
    
    // 返回渲染函数
    return () =>
      <div class="child-container" style="margin-top: 20px">
        <h3>子组件</h3>
        <p>接收父组件Props: {props.message}</p>

        {/* 渲染传入的 items 列表 */}
        <ul>
          {props.items.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>

        {/* 输入框,双向绑定 inputValue */}
        <input
          type="text"
          v-model={inputValue.value}
          placeholder="输入内容回传父组件"
        />
        <button onClick={sendToParent}>发送数据到父组件</button>

        {/* 渲染插槽内容,如果没有提供默认插槽内容则显示 '默认插槽内容' */}

        <div class="slot-area">
          {slots.default ? slots.default({ text: childMessage.value }) : '默认插槽内容'}
        </div>
      </div>
  }
};

2. 父组件下发数据

javascript 复制代码
import {  ref } from 'vue';
import ChildComponent from './child.jsx';

export default {
  name: 'ParentComponent',

  setup () {
    // 父组件状态
    const parentMessage = ref('来自父组件的消息');
    const childData = ref(null);
    const items = ref(['Apple', 'Banana', 'Cherry']);

    // 接收子组件事件的方法
    function handleChildEvent  (data) {
      console.log('收到子组件数据:', data);
      childData.value = data;
    };

    // 更新列表方法
    function updateItems() {
      items.value = [...items.value, 'New Item ' + Date.now()];
    };

    return () =>
      <div class="parent-container">
        <h2>父组件</h2>
        <p>父组件消息: {parentMessage.value}</p>
        <p>来自子组件的数据: {JSON.stringify(childData.value)}</p>

        {/* 基础Props传递 */}
        <ChildComponent
          message={parentMessage.value}
          items={items.value}
          onChildEvent={handleChildEvent}
        />

        <button onClick={updateItems}>更新列表</button>
      </div>

  }
};
相关推荐
JSON_L2 小时前
Vue rem回顾
前端·javascript·vue.js
brzhang4 小时前
颠覆你对代码的认知:当程序和数据只剩下一棵树,能读懂这篇文章的人估计全球也不到 100 个人
前端·后端·架构
斟的是酒中桃4 小时前
基于Transformer的智能对话系统:FastAPI后端与Streamlit前端实现
前端·transformer·fastapi
烛阴4 小时前
Fract - Grid
前端·webgl
JiaLin_Denny5 小时前
React 实现人员列表多选、全选与取消全选功能
前端·react.js·人员列表选择·人员选择·人员多选全选·通讯录人员选择
brzhang5 小时前
我见过了太多做智能音箱做成智障音箱的例子了,今天我就来说说如何做意图识别
前端·后端·架构
为什么名字不能重复呢?5 小时前
Day1||Vue指令学习
前端·vue.js·学习
eternalless5 小时前
【原创】中后台前端架构思路 - 组件库(1)
前端·react.js·架构
Moment5 小时前
基于 Tiptap + Yjs + Hocuspocus 的富文本协同项目,期待你的参与 😍😍😍
前端·javascript·react.js
Krorainas6 小时前
HTML 页面禁止缩放功能
前端·javascript·html