大家好,我是yma16,本文分享关于vue、react组件数据传值对比分析——父组件传递子组件,子组件传递父组件。
react渲染原理
React 是一个基于组件的 JavaScript 库,用于构建用户界面。React 的主要原理是将用户界面抽象为一组嵌套的组件,每个组件都拥有自己的状态和行为。当组件的状态发生改变时,React 会自动重新渲染组件,并将更新后的组件插入到 DOM 树中。
React 的渲染过程主要涉及以下几个步骤:
首先,React 会根据 JSX 语法解析出虚拟 DOM(Virtual DOM)对象树,该虚拟 DOM 对象树只是一个 JavaScript 对象,其中包含了组件的状态、属性和子节点等信息。
然后,React 通过比较新旧虚拟 DOM 对象树,找出需要更新的部分,只更新需要更新的部分,称之为 “DOM Diff”。
接着,React 调用 render 方法生成新的虚拟 DOM 对象树,并将其与旧的虚拟 DOM 对象树进行比较。
如果新旧虚拟 DOM 对象树相同,则不进行任何操作。
如果新旧虚拟 DOM 对象树不同,则根据差异进行更新,生成新的虚拟 DOM 对象树。
最后,React 将更新后的虚拟 DOM 对象树渲染到真实的 DOM 上,完成渲染过程。
React 的渲染过程主要基于虚拟 DOM 和差异化算法。通过虚拟 DOM 对象树的比较,React 能够高效地进行局部更新,提高了应用程序的性能和用户体验。
vue渲染原理
Vue的渲染原理可以大致分为以下几个步骤:
解析模板:Vue会将模板字符串解析成抽象语法树(AST),这个过程中会标记出模板中的所有指令、插值语法、事件等信息。这一步由模板编译器完成。
根据AST生成渲染函数:Vue会从抽象语法树中生成一个可执行的渲染函数(render function),这个函数可以接收一个参数——渲染上下文(render context),并返回一个VNode节点。
渲染函数执行:当组件需要重新渲染时,Vue会执行渲染函数,生成一个新的VNode节点树。
Diff算法对比新旧VNode:Vue将上一步生成的新VNode节点树和上一次渲染的旧VNode节点树进行对比,通过Diff算法找出需要更新的节点。
生成补丁(patch):Diff算法找出需要更新的节点后,会生成一个补丁对象(patch),这个补丁对象描述了要对哪些节点进行何种修改操作。
将补丁应用到真实的DOM上:最后,Vue将生成的补丁对象应用到真实的DOM节点上,完成组件的更新。
注:以上是Vue2.x版本的渲染原理,Vue3.x版本的渲染原理有所不同,主要是采用了基于Proxy的响应式数据自动更新机制以及模板编译器与运行时渲染器分离等新特性。
App.tsx通过标签内属性传递editInstance给EmailPage.tsx
父组件 App.txs
传递一个grapesjs实例到EmailPage
import './App.css';
import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
function App() {
return (
<div className="App">
<EmailPage editInstance={grapesjs} ></EmailPage>
</div>
);
}
export default App;
子组件 EmailPage.tsx 解构props
解构接收props的editorInstance
import grapesJSMJML from '../components/email-edit/index'
import { forwardRef, useEffect, useState,useImperativeHandle } from 'react'
const EmailPage=(props:any)=>{
const [editor,setEditor]=useState();
useEffect(()=>{
const editorInstance:any = props.editInstance
.init({
fromElement: true,
container: '#gjs-email',
plugins: [grapesJSMJML ],
});
try{
editorInstance.Commands.run('mjml-clear')
}
catch (e) {
console.error('e',e)
}
setEditor(editorInstance)
},[props.editInstance])
return (
<div id={'gjs-email'} className={'design-editor'}/>
)
}
export default EmailPage;
同理我们也可以在props传递一个事件给props,在子组价触发即可
父组件 App.txs 使用ref获取组件实例
import './App.css';
import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
import { useState,useEffect,useRef } from 'react';
function App() {
const emailRef:any=useRef();
useEffect(()=>{console.log(emailRef)},[emailRef])
return (
<div className="App">
<EmailPage editInstance={grapesjs} ></EmailPage>
</div>
);
}
export default App;
子组件 EmailPage.txs使用useImperativeHandle 暴露方法和属性
暴露两个方法分别是 getHtml和getBodyContent,最后使用forwardRef抛出组件实例
import grapesJSMJML from '../components/email-edit/index'
import { forwardRef, useEffect, useState,useImperativeHandle } from 'react'
import zh from "../components/email-edit/locale/zh";
const EmailPage=(props:any,ref:any)=>{
const [editor,setEditor]=useState();
const [domRef,setDomRef]=useState();
useEffect(()=>{
const editorInstance:any = props.editInstance
.init({
fromElement: true,
container: '#gjs-email',
plugins: [grapesJSMJML ],
});
try{
editorInstance.Commands.run('mjml-clear')
}
catch (e) {
console.error('e',e)
}
setEditor(editorInstance)
},[props.editInstance])
const getBodyContent=()=>{
// @ts-ignore
const inlineHtml=editor.Commands.run('mjml-code-to-html-inline')
const matchBody=new RegExp(']*>([\\s\\S]+?)<\\/body>','ig');
const matchBodyText=inlineHtml.match(matchBody)
// @ts-ignore
return matchBodyText?matchBodyText[0]:''
};
const getHtml=()=>{
// @ts-ignore
return editor.Commands.run('mjml-code-to-html-inline')
}
useImperativeHandle(ref, () => ({
getHtml:getHtml,
getBodyContent:getBodyContent
}));
return (
<div id={'gjs-email'} className={'design-editor'}
ref={(ref:any)=>{
setDomRef(ref)
}}
/>
)
}
export default forwardRef(EmailPage);
子组件 defineProps 定义接受的参数
IframeContent.vue
<template>
<div class="iframe-container">
<div class="iframe-content" v-if="!isPage&&!isModel">
<div style="width: 100%">
<span> 标题:{{ title }} </span>
{{kind}}
<a-button
@click="jumpPage"
type="primary"
style="float: right; margin: 5px"
>
跳转</a-button
>
</div>
<iframe :src="url" class="iframe-box"></iframe>
</div>
<div class="iframe-content" v-else-if="!isModel">
<UserTable></UserTable>
</div>
<div class="iframe-content" v-else>
<ChatTable></ChatTable>
</div>
</div>
</template>
<script lang="ts" setup>
import {computed} from 'vue'
// @ts-ignore
const props = defineProps<{
url: string;
title: string;
kind: string;
}>();
const isPage=computed(()=>{
console.log('props',props)
return props.kind=='page'
})
const isModel=computed(()=>{
console.log('props',props)
return props.kind=='model'
})
const emit = defineEmits<{
(e: "change", id: number): void;
(e: "update", value: string): void;
}>();
const jumpPage = () => {
window.open(props.url);
};
</script>
父组件 标签内传递数据给子组件
传递参数给iframe-content组件
<script setup lang="ts">
// @ts-ignore
import IframeContent from "../iframe/IframeContent.vue";
import { reactive} from "vue";
interface contentType {
url: string;
title: string;
kind:string;
}
const contentConfig: contentType = reactive({
url: "url",
title: "title",
kind:'kind'
});
</script>
<template>
<iframe-content
:url="contentConfig.url"
:title="contentConfig.title"
:kind="contentConfig.kind"
/>
</template>
子组件
<template>
<div class="iframe-container">
<div class="iframe-content" v-if="!isPage&&!isModel">
<div style="width: 100%">
<span> 标题:{{ title }} </span>
{{kind}}
<a-button
@click="jumpPage"
type="primary"
style="float: right; margin: 5px"
>
跳转</a-button
>
</div>
<iframe :src="url" class="iframe-box"></iframe>
</div>
<div class="iframe-content" v-else-if="!isModel">
<UserTable></UserTable>
</div>
<div class="iframe-content" v-else>
<ChatTable></ChatTable>
</div>
</div>
</template>
<script lang="ts" setup>
import {computed} from 'vue'
// @ts-ignore
const props = defineProps<{
url: string;
title: string;
kind: string;
}>();
const isPage=computed(()=>{
console.log('props',props)
return props.kind=='page'
})
const isModel=computed(()=>{
console.log('props',props)
return props.kind=='model'
})
const emit = defineEmits<{
(e: "change", id: number): void;
(e: "update", value: string): void;
}>();
const jumpPage = () => {
window.open(props.url);
};
</script>
父组件通过@绑定事件change和update就能接受子组件触发的change和update事件
通过使用ref绑定formRef去获取校验事件
<template>
<div class="container">
<div class="loginUser-container">
<div class="loginUser-title">管理平台</div>
<a-form
:model="state.formState"
:label-col="state.layoutConfig.labelCol"
:wrapper-col="state.layoutConfig.wrapperCol"
:rules="state.formRule"
ref="formRef"
layout="vertical"
autocomplete="off"
>
<a-form-item label="账号" name="username">
<a-input
v-model:value="state.formState.username"
allowClear
placeholder="请输入账号"
:disabled="state.spinning"
/>
</a-form-item>
<a-form-item label="密码" name="password">
<a-input-password
v-model:value="state.formState.password"
:disabled="state.spinning"
allowClear
placeholder="请输入密码"
/>
</a-form-item>
<a-form-item name="remember" :wrapper-col="state.wrapperCol">
<a-checkbox
v-model:checked="state.formState.remember"
:disabled="state.spinning"
>记住密码</a-checkbox
>
</a-form-item>
<a-form-item :wrapper-col="state.submitWrapperCol" class="submit-box">
<a-button
type="primary"
html-type="submit"
@click="loginAction"
:loading="state.spinning"
style="width: 100%; font-size: 16px; font-weight: bolder"
>登录</a-button
>
</a-form-item>
</a-form>
<div class="description">
<span class="description-prefix">没账号?</span>
<span
@click="jumpRegister"
class="description-after"
:disabled="state.spinning"
>去注册</span
>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { message } from "ant-design-vue";
import { loginUser } from "../../service/user/userApi";
import type { FormInstance } from "ant-design-vue";
interface FormStateType {
username: string;
password: string;
remember: boolean;
}
interface FormRuleType {
username: Object;
password: Object;
}
interface stateType {
formState: FormStateType;
formRule: FormRuleType;
layoutConfig: any;
wrapperCol: any;
submitWrapperCol: any;
spinning: boolean;
backgroundImgUrl: string;
}
// 路由
const router = useRouter();
//store
const store = useStore();
const formRef = ref<FormInstance>();
const state: stateType = reactive({
formState: {
username: "",
password: "",
remember: false,
},
spinning: false,
formRule: {
username: [{ required: true, message: "请输入账号!" }],
password: [{ required: true, message: "请输入密码!" }],
},
layoutConfig: {
labelCol: {
span: 8,
},
wrapperCol: {
span: 24,
},
},
wrapperCol: { offset: 0, span: 24 },
submitWrapperCol: { offset: 0, span: 24 },
backgroundImgUrl:
"http://www.yongma16.xyz/staticFile/common/img/background.png",
});
/**
* 初始化表单内容
*/
const initForm = () => {
const userInfoItem: any = window.localStorage.getItem("userInfo");
interface userInfoType {
username: string;
password: string;
remember: boolean;
}
const userInfo: userInfoType = userInfoItem
? JSON.parse(userInfoItem)
: {
username: "",
password: "",
remember: false,
};
if (userInfo.username && userInfo.password) {
state.formState.username = userInfo.username;
state.formState.password = userInfo.password;
state.formState.remember = userInfo.remember;
}
};
/**
* 前往注册!
*/
const jumpRegister = () => {
// 带 hash,结果是 /about#team
router.push({ path: "/register" });
};
/**
* 前往home!
*/
const jumpHome = () => {
// 带 hash,结果是 /about#team
router.push({ path: "/" });
};
/**
* 记住密码
* @param params
*/
const rememberAction = (params: Object) => {
window.localStorage.setItem("userInfo", JSON.stringify(params));
};
/**
* 登录
*/
const loginAction = () => {
if (formRef.value) {
formRef.value.validate().then(async (res: any) => {
state.spinning = true;
const params = {
username: state.formState.username,
password: state.formState.password,
};
if (state.formState.remember) {
rememberAction({ ...params, remember: state.formState.remember });
}
try {
console.log('登录',params)
// @ts-ignore
await store.dispatch(
"user/loginUser",
params
);
// 跳转
setTimeout(() => {
jumpHome();
}, 500);
state.spinning = false;
} catch (r: any) {
message.error(JSON.stringify(r));
state.spinning = false;
throw Error(r);
}
});
}
};
onMounted(() => {
//初始化
initForm();
});
</script>
综合比较react和vue,对于选型我分成两种情况讨论。
个人比较看好vue,有以下3点。
a. 国内开发人数众多,搭建可以用中文在社区交流vue3,扩大国内开发影响力
b. vue3的组件传值等我都比react好用,react用的费劲
c. vue的生态良好,版本也一直在迭代更新
本文分享到这结束,如有错误或者不足之处欢迎指出!
点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
感谢你的阅读!