前言
相应有很多前端er工作中长期接触Vue2,在面对技术栈切换到Vue3或React常常无从下手,可能平时零星接触一些知识点,但缺乏项目的实战,担心自己不能够hold住项目,下面这边将结合实战中常用的场景来帮助大家更好地理解"框架工具",从Vue2丝滑过渡技术栈!
首先,先讲讲这三个框架的一些显著特点
- Vue2: 模版语法、选项式API、组件内包含this指向、mixin实现逻辑复用
- Vue3: 紧凑式API、组件无this指向(提供实例概念)、reactive + hooks式逻辑复用、 更好地结合TS
- React: 单向数据流、jsx、tsx语法、函数式组件、useState + hooks式逻辑复用
下面开始讲讲三个框架在各应用场景下的使用:
1、组件间通信
父组件传数据到子组件
1、Vue2:父组件通过属性传递参数,子组件采用props接收
xml
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: 'Hello from parent component'
};
}
};
</script>
<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
props: ['message']
};
</script>
2、Vue3:父组件同样属性传递数据,子组件接收中props 需要使用 defineProps()
这个宏函数来进行声明
xml
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage"></child-component>
</div>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
const parentMessage = ref('Hello from parent component');
return {
parentMessage
};
}
};
</script>
<!-- 子组件 -->
<script setup>
const props = defineProps({
message: {
type: String,
required: true
}
})
</script>
<template>
<div>{{ message }}</div>
<div>{{ props.message }}</div>
</template>
注:defineProps 、defineEmits 、 defineExpose 和 withDefaults 这四个宏函数只能在 <script setup> 中使用。他们不需要导入,会随着 <script setup> 的处理过程中一起被编译。
3、React:父组件通过属性传递参数,子组件采用props接收
javascript
<!-- 父组件 -->
import React from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const parentMessage = 'Hello from parent component';
return (
<div>
<ChildComponent message={parentMessage} />
</div>
);
}
export default ParentComponent;
<!-- 子组件 -->
import React from 'react';
const ChildComponent = (props) => {
const { message } = props;
return (
<div>
<p>{message}</p>
</div>
);
}
export default ChildComponent;
子组件传数据到父组件
1、Vue2:子组件向父组件传值通常通过自定义事件的方式实现。首先,在子组件中使用$emit
方法触发一个自定义事件,并传递需要传递给父组件的值。然后,在父组件中监听该自定义事件,并在对应的方法中获取传递的值
xml
<!-- 子组件 -->
<template>
<button @click="sendValue">传递值给父组件</button>
</template>
<script>
export default {
methods: {
sendValue() {
this.$emit('custom-event', '传递的值');
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<p>接收到的值:{{ receivedValue }}</p>
<child-component @custom-event="handleEvent"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
receivedValue: ''
};
},
methods: {
handleEvent(value) {
this.receivedValue = value;
}
}
}
</script>
2、Vue3:子组件向父组件传值的方式略有不同,采用defineEmits
xml
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['someEvent'])
function onClick() {
emit('someEvent', 'child message')
}
</script>
<template>
<button @click="onClick">点击</button>
</template>
<!-- 父组件 -->
<script setup>
import ChildView from './ChildView.vue'
function someEvent(value) {
console.log(value) // child message
}
</script>
<template>
<ChildView @some-event="someEvent" />
</template>
3、React:父组件通过props向子组件传递一个回调函数的方式,并由子组件在需要时调用该回调函数,并将需要传递给父组件的值作为参数传递
javascript
<!-- 父组件 -->
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [receivedValue, setReceivedValue] = useState('');
const handleValue = (value) => {
setReceivedValue(value);
}
return (
<div>
<p>接收到的值:{receivedValue}</p>
<ChildComponent onSendValue={handleValue} />
</div>
);
}
export default ParentComponent;
<!-- 子组件 -->
import React from 'react';
function ChildComponent(props) {
const sendValue = () => {
props.onSendValue('传递的值');
}
return (
<button onClick={sendValue}>传递值给父组件</button>
);
}
export default ChildComponent;
跨层级组件传递
1、Vue2:跨层级的组件之间传递参数,通常会使用provide
和inject
来实现。provide
用于在父组件中提供数据,而inject
用于在子组件中注入并使用这些数据
xml
<!-- 父组件 -->
<template>
<div>
<p>父组件提供的值:{{ providedValue }}</p>
<child-component></child-component>
</div>
</template>
<script>
export default {
data() {
return {
providedValue: '跨层级传递的参数'
};
},
provide() {
return {
providedValue: this.providedValue
};
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<p>从父组件注入的值:{{ injectedValue }}</p>
</div>
</template>
<script>
export default {
inject: ['providedValue'],
computed: {
injectedValue() {
return this.providedValue;
}
}
}
</script>
2、Vue3:类似使用provide
和inject
来实现
xml
<!-- 父组件 -->
<template>
<div>
<p>父组件提供的值:{{ providedValue }}</p>
<child-component />
</div>
</template>
<script setup>
import { provide, ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const providedValue = ref('跨组件传递的参数');
provide('providedValue', providedValue);
</script>
<!-- 子组件 -->
<template>
<div>
<p>从父组件传递的值:{{ injectedValue }}</p>
</div>
</template>
<script setup>
import { inject, defineEmits } from 'vue';
const injectedValue = inject('providedValue');
</script>
3、React:使用上下文(Context)传值,通过创建上下文对象,将数据传递给所有的子组件。子组件通过useContext
钩子或Consumer
组件来获取上下文中的值。上下文可以在整个组件树中共享数据。
xml
<!-- 父组件 -->
const MyContext = React.createContext();
function ParentComponent() {
const sharedValue = "跨组件传递的值";
return (
<MyContext.Provider value={sharedValue}>
<ChildComponent />
</MyContext.Provider>
);
}
<!-- 子组件 -->
function ChildComponent() {
const sharedValue = React.useContext(MyContext);
return <p>从父组件传递的值:{sharedValue}</p>;
}
2、计算属性
1、Vue2:在 Vue 2 中,计算属性是通过定义一个具有 get
方法的函数来创建的。这个函数会被设置为计算属性的 getter
javascript
// Vue 2
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
};
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
},
},
};
<!-- 使用计算属性 -->
<template>
<div>
<p>Full Name: {{ fullName }}</p>
</div>
</template>
2、Vue3:可以使用 computed
函数来创建类似于计算属性的行为
javascript
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value;
});
return {
firstName,
lastName,
fullName,
};
},
};
<!-- 使用计算属性 -->
<template>
<div>
<p>Full Name: {{ fullName }}</p>
</div>
</template>
3、React:没有直接等同于 Vue 中的计算属性的概念,但它可以使用 useMemo 钩子函数,避免在每次组件渲染时都重新计算某个值,你可以使用 React 的 useMemo
钩子函数。useMemo
接收一个回调函数和依赖项数组,并返回计算后的值。只有在依赖项发生变化时,才会重新计算。
javascript
import { useMemo } from 'react';
function MyComponent({ firstName, lastName }) {
const fullName = useMemo(() => {
return firstName + ' ' + lastName;
}, [firstName, lastName]);
return <p>Full Name: {fullName}</p>;
}
3、Watch
1、Vue2:在 Vue 2 中,你可以通过在组件的 watch
选项中定义一个或多个观察表达式来观察数据变化,并在回调函数中对变化做出相应的处理。
javascript
// Vue 2
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
};
},
watch: {
firstName(newFirstName, oldFirstName) {
console.log('firstName changed', newFirstName, oldFirstName);
},
lastName(newLastName, oldLastName) {
console.log('lastName changed', newLastName, oldLastName);
},
},
};
2、Vue3:在 Vue 3 中,watch
函数已被重构为更加灵活的 watchEffect
和 watch
函数的组合
-
watchEffect
:会立即执行传入的回调函数,并自动追踪其内部所使用的响应式依赖,并在依赖发生改变时重新运行回调函数。javascriptimport { watchEffect } from 'vue'; export default { setup() { const firstName = ref('John'); watchEffect(() => { console.log('firstName changed', firstName.value); }); return { firstName, }; }, };
-
watch
:可以通过设置watch
函数来手动监视特定的响应式依赖项,并在其发生改变时执行回调函数javascriptimport { watch, ref } from 'vue'; export default { setup() { const firstName = ref('John'); const lastName = ref('Doe'); watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => { console.log('firstName or lastName changed', newFirstName, newLastName, oldFirstName, oldLastName); }); return { firstName, lastName, }; },
3、React:在函数组件中,你可以使用 React 的 useEffect
钩子函数来监听特定的状态或属性,并在其发生变化时执行相应的操作
javascript
import { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count changed', count);
// 执行其他操作...
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
上述示例中,通过传递一个依赖项数组 [count] 给 useEffect,它会在 count 发生变化时执行回调函数。
4、路由使用
1、Vue2:通常是使用 this.$router
或 this.$route
来进行路由的跳转和参数获取
xml
<script>
function onClick() {
this.$router.push({
path: '/about',
query: {
msg: 'hello vue3!'
}
})
}
const route = this.$route
console.log(route.query.msg) // hello vue3!
</script>
2、Vue3:以使用 vue-router
提供的 useRouter
方法,来进行路由跳转
xml
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
function onClick() {
router.push({
path: '/about',
query: {
msg: 'hello vue3!'
}
})
}
const route = useRoute()
console.log(route.query.msg) // hello vue3!
</script>
3、React:使用React Router库进行路由跳转和获取路由对象,通过调用history
对象的push
方法,可以进行编程式导航跳转,获取路由对象:可以通过useParams
、useLocation
和useHistory
等React Router提供的钩子函数来获取路由对象
javascript
ex1:
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
history.push('/path');
};
return (
<div>
<button onClick={handleClick}>Go to Path</button>
</div>
);
}
ex2:
import { useParams } from 'react-router-dom';
function MyComponent() {
const { id } = useParams();
// 使用路由参数id进行特定操作
return (
<div>
<h1>Post {id}</h1>
</div>
);
}
ex3:
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
// 使用location对象获取当前URL、查询参数等信息
return (
<div>
<p>Current URL: {location.pathname}</p>
<p>Query Params: {location.search}</p>
</div>
);
}
5、上下文对象
1、Vue2:Vue2存在this这个上下文对象,其数据、方法等都可以用this.xx来获取,这边就不举例。
2、Vue3:Vue3 的 setup
中无法使用 this
这个上下文对象,它可以通过 getCurrentInstance
方法获取上下文对象。
xml
<script setup>
import { getCurrentInstance } from 'vue'
// 以下两种方法都可以获取到上下文对象
const { ctx } = getCurrentInstance()
const { proxy } = getCurrentInstance()
</script>
注:ctx 只能在开发环境使用,生成环境为 undefined 。 推荐使用 proxy ,在开发环境和生产环境都可以使用
3、React:在React中,组件没有类似于Vue 3的实例引用的概念
6、插槽
1、Vue2:有两种类型的插槽:具名插槽和作用域插槽
-
具名插槽:可以在父组件中使用
<slot>
元素,并通过name
属性来指定插槽的名称。子组件中使用slot
元素并设置slot
属性的值为对应的插槽名称,父组件中内容会被插入到对应的插槽中。xml<!-- 父组件 --> <template> <div> <slot name="header"></slot> <slot></slot> <slot name="footer"></slot> </div> </template> <!-- 使用父组件 --> <template> <my-component> <template v-slot:header> <!-- 插入到名称为 "header" 的插槽中 --> <h1>Header Slot Content</h1> </template> <p>Default Slot Content</p> <template v-slot:footer> <!-- 插入到名称为 "footer" 的插槽中 --> <footer>Footer Slot Content</footer> </template> </my-component> </template>
-
作用域插槽:作用域插槽允许父组件向子组件传递数据。可以在父组件中使用带有参数的
<slot>
元素,并在子组件中使用具有相同参数的<template>
元素xml<!-- 父组件 --> <template> <div> <slot :item="data"></slot> </div> </template> <!-- 使用父组件 --> <template> <my-component> <template v-slot="{ item }"> <!-- 使用传递的数据 --> <p>{{ item }}</p> </template> </my-component> </template>
2、Vue3:插槽的概念发生了变化,被合并为一个更通用的组合式 API:<slot>
元素被替换为 v-slot
指令,并且可以直接在组件标签上使用。使用 v-slot
指令时,可以通过参数语法或缩写语法来命名插槽
xml
ex1:
<!-- 父组件 -->
<template>
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
<!-- 使用父组件 -->
<template>
<my-component>
<template v-slot:header>
<!-- 插入到名称为 "header" 的插槽中 -->
<h1>Header Slot Content</h1>
</template>
<p>Default Slot Content</p>
<template v-slot:footer>
<!-- 插入到名称为 "footer" 的插槽中 -->
<footer>Footer Slot Content</footer>
</template>
</my-component>
</template>
ex2:(作用域插槽)
<!-- 父组件 -->
<template>
<div>
<slot :item="data"></slot>
</div>
</template>
<!-- 使用父组件 -->
<template>
<my-component>
<template v-slot:default="slotProps">
<!-- 使用传递的数据 -->
<p>{{ slotProps.item }}</p>
</template>
</my-component>
</template>
3、React:通过组件的props来实现的,有两种常见的插槽使用方式
-
使用Props作为插槽:父组件可以通过子组件的props将内容传递给子组件进行渲染
javascript// 父组件 function ParentComponent() { return ( <div> <ChildComponent> {/* 子组件插槽内容 */} <h1>插槽内容</h1> </ChildComponent> </div> ); } // 子组件 function ChildComponent(props) { return ( <div> {/* 渲染插槽内容 */} {props.children} </div> ); }
-
使用组件作为插槽:父组件可以通过自定义组件作为插槽,在子组件中使用该组件进行插槽渲染。
javascript// 父组件 function ParentComponent() { return ( <div> <ChildComponent header={<h1>Header Slot Content</h1>} footer={<footer>Footer Slot Content</footer>}> {/* 默认插槽内容 */} <p>Default Slot Content</p> </ChildComponent> </div> ); } // 子组件 function ChildComponent(props) { return ( <div> {/* 渲染插槽内容 */} {props.header} {props.children} {props.footer} </div> ); }
7、生命周期
1、Vue2:生命周期方法在组件实例创建、挂载和销毁时被调用,使用 beforeCreate
, created
, beforeMount
, mounted
, beforeUpdate
, updated
, beforeDestroy
, destroyed
等生命周期方法。
2、Vue3:使用 setup
函数来替代大部分的生命周期方法,它在组件的 beforeCreate
钩子之前调用,可以在 setup
函数中执行组件初始化的逻辑。使用 onBeforeMount
、onMounted
、onBeforeUpdate
、onUpdated
、onBeforeUnmount
、onUnmounted
等新的生命周期方法。还引入了两个全局的生命周期方法 onRenderTracked
和 onRenderTriggered
,用于跟踪和调试渲染过程。
3、React:在 React 中,组件的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。
arduino
1、挂载阶段(Mounting):
constructor:在组件被创建时调用,用于初始化组件的状态和绑定事件处理程序。
static getDerivedStateFromProps:在组件实例化和接收到新的 props 时调用,用于根据新的 props 更新组件状态。
render:必选的生命周期方法,返回组件的 JSX 元素并渲染到页面上。
componentDidMount:在组件被渲染到页面后调用,可以进行异步数据的获取、订阅事件等操作。
2、更新阶段(Updating):
static getDerivedStateFromProps:同挂载阶段中的用法,根据新的 props 更新组件状态。
shouldComponentUpdate:在组件更新之前调用,可以根据新的 props 或 state 决定是否需要重新渲染组件。
render:重新渲染组件。
getSnapshotBeforeUpdate:在组件更新之前被调用,可以获取之前的 DOM 信息,返回值将作为第三个参数传递给 componentDidUpdate 方法。
componentDidUpdate:在组件更新完成后调用,可以进行 DOM 操作、发起网络请求等。
3、卸载阶段(Unmounting):
componentWillUnmount:在组件被卸载之前调用,可以进行清理工作,如取消订阅、清除定时器等。
8、逻辑复用
1、Vue2:使用 Mixins 来实现逻辑的复用,但在多个 Mixin 之间可能存在命名冲突和相互依赖的问题
javascript
// 定义一个 Mixin 对象
var myMixin = {
data: function () {
return {
message: 'Hello, world!'
}
},
methods: {
showMessage: function () {
alert(this.message)
}
}
}
// 组件引入 Mixin
var myComponent = Vue.extend({
mixins: [myMixin],
created: function () {
console.log(this.message) // 输出 "Hello, world!"
}
})
2、Vue3:引入了 Composition API,通过函数式组合的方式来实现逻辑的复用,可以更好地组织和封装代码
javascript
import { ref, computed } from 'vue'
// 定义一个逻辑
function useCounter() {
const count = ref(0)
function increment() {
count.value++
}
function decrement() {
count.value--
}
const doubledCount = computed(() => count.value * 2)
return {
count,
doubleCount,
increment,
decrement
}
}
// 组件引入逻辑
export default {
setup() {
const { count, doubleCount, increment, decrement } = useCounter()
return {
count,
doubleCount,
increment,
decrement
}
}
}
3、React:可以使用 Hooks 来实现逻辑的复用。Hooks 实际上是一个函数,用于增强函数组件的功能,使之具有类似于类组件的生命周期、状态管理等功能。(Hooks 只能用于函数组件和自定义 Hooks 中,类组件中不可使用)
javascript
import React, { useState, useEffect } from 'react'
// 定义一个逻辑
function useCounter() {
const [count, setCount] = useState(0)
function increment() {
setCount(count + 1)
}
function decrement() {
setCount(count - 1)
}
useEffect(() => {
console.log('count has changed:', count)
}, [count])
return {
count,
increment,
decrement
}
}
// 组件引入逻辑
function MyComponent() {
const { count, increment, decrement } = useCounter()
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
9、全局API
1、Vue2:Vue2 中的全局属性或全局方法,是在构造函数 Vue 的原型对象上进行添加,如:Vue.prototype.$axios = axios
,通过this.$axios进行调用。
2、Vue3:在 app
实例上添加:
arduino
// main.js
app.config.globalProperties.$axios = axios
在组件中使用:
xml
<script setup>
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
proxy.$axios.get('http://...')
</script>
3、React:在 React 中,可以通过将全局 API 注册到应用的上下文中,以便在整个应用的任何地方使用。一种常见的方式是使用 Context API 或第三方库,如 react-helmet。
javascript
1、创建一个新的文件,例如 api.js,用于定义全局 API:
import React, { createContext, useContext } from 'react';
// 创建一个上下文对象
const GlobalAPIContext = createContext();
// 提供一个自定义 Hook 用于获取全局 API
export function useGlobalAPI() {
return useContext(GlobalAPIContext);
}
// 全局 API 提供者组件
export function GlobalAPIProvider({ children }) {
// 定义全局 API 的值和方法
const globalAPI = {
// 在这里定义你的全局 API
showMessage: (message) => {
alert(message);
},
// ...
};
return (
// 使用上下文 Provider 包装子组件,并传递全局 API
<GlobalAPIContext.Provider value={globalAPI}>
{children}
</GlobalAPIContext.Provider>
);
}
2、在根组件中使用全局 API 提供者,并将应用的内容包裹在其中
import React from 'react';
import { GlobalAPIProvider } from './api.js';
function App() {
return (
// 使用 GlobalAPIProvider 包裹应用的内容
<GlobalAPIProvider>
{/* 应用的其他组件 */}
</GlobalAPIProvider>
);
}
export default App;
3、在需要使用全局 API 的组件中,使用 useGlobalAPI 自定义 Hook 获取全局 API:
import React from 'react';
import { useGlobalAPI } from './api.js';
function MyComponent() {
// 使用 useGlobalAPI 自定义 Hook 获取全局 API
const globalAPI = useGlobalAPI();
// 调用全局 API 的方法
const handleClick = () => {
globalAPI.showMessage('Hello, World!');
};
return (
<button onClick={handleClick}>Show Message</button>
);
}
export default MyComponent;