一、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. 实际等效代码:
javascriptimport { 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}
/>
相当于:
- 将
state.msg
作为初始值传递给输入框- 当输入框值变化时,通过事件更新
state.msg
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. 虚拟滚动优化长列表
javascriptimport { 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. 子组件
接收数据:
setup (props, { emit, slots })
接收props
- 书写
props
对象
dartexport 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>
}
};