Vue〡React 写法对照指南
一、状态值
- React-Hook
- React-Class
- Vue-Composition
- Vue-JSX
- Vue-Options
import React, { useState } from 'react'
const App = (props) => {
const [count, setCount] = useState(0)
const handleAdd = () => {
setCount(count + 1)
}
return (
<>
<span>{count}</span>
<button onClick={handleAdd}>+1</button>
</>
)
}
export default App
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
function handleAdd () {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<>
<span>{this.state.count}</span>
<button onClick={handleAdd}>+1</button>
</>
)
}
}
export default App
<template>
<span>{{ count }}</span>
<button @click="handleAdd">+1</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const handleAdd = () => {
count.value += 1
}
</script>
import { ref, defineComponent } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const count = ref(0)
const handleAdd = () => {
count.value = count.value + 1
}
return () => {
return (
<>
<span>{count.value}</span>
<button onClick={handleAdd}>+1</button>
</>
);
};
},
});
<template>
<span>{{ count }}</span>
<button @click="handleAdd">+1</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
handleAdd() {
this.count += 1
}
}
}
</script>
以下对照指南以 React Hook、Vue-Composition
、Vue-JSX
的写法为例。
二、显示隐藏
- React | Vue-JSX
- Vue
<>
{show && <Button>按钮</Button>}
{show ? <Button>显示</Button> : <Button>隐藏</Button>}
</>
<template>
<i-button v-if="show">按钮</i-button>
<i-button v-if="show">显示</i-button>
<i-button v-else>隐藏</i-button>
</template>
三、属性传值
- React
- Vue
- Vue-JSX
<Button type="primary">按钮</Button>
<Button disabled>按钮</Button>
<Button loading={true}>按钮</Button>
<Button name={getName()}>按钮</Button>
<Button onClick={handleAdd}>按钮</Button>
<i-button type="primary">按钮</i-button>
<i-button disabled>按钮</i-button>
<i-button :loading="true">按钮</i-button>
<i-button :name={getName()}>按钮</i-button>
<i-button @click="handleAdd">按钮</i-button>
<i-button type="primary">按钮</i-button>
<i-button disabled>按钮</i-button>
<i-button loading={true}>按钮</i-button>
<i-button name={getName()}>按钮</i-button>
<i-button onClick={handleAdd}>按钮</i-button>
四、类名 & 样式
1、内联写法
- React
- Vue
- Vue-JSX
<div
className={classNames(
'i-button',
`i-button--type-${type}`,
className,
)}
>
按钮
</div>
<div
style={{
width: 300,
fontSize: 16
}}
>
按钮
</div>
<div
:class="[
'i-button',
`i-button--type-${type}`
]"
>
按钮
</div>
<div
:style="{
width: '300px',
fontSize: '16px'
}"
>
按钮
</div>
<div
class={[
'i-button',
`i-button--type-${type.value}`,
]}
>
按钮
</div>
<div
style={{
width: 300,
fontSize: 16
}}
>
按钮
</div>
2、分离写法
- React
- Vue
- Vue-JSX
import React, { useState } from 'react';
import classNames from 'classnames';
import './index.scss';
const App = () => {
const [type, setType] = useState('default')
const cls = classNames(
'i-button',
`i-button--type-${type}`
);
const style = {
width: 300,
fontSize: 16
}
return (
<div className={cls} style={style}>按钮</div>
);
};
export default App;
<template>
<div :class="cls" :style="style">按钮</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const cls = computed(() => [
'i-button',
`i-button--type-${type}`,
])
const style = computed(() => [
{
width: '300px',
fontSize: '16px'
}
])
</script>
<style lang="scss">
@import './index.scss';
</style>
import { computed, defineComponent, ref } from 'vue';
import './index.scss';
export default defineComponent({
name: 'App',
setup() {
const type = ref('default')
const cls = computed(() => [
'i-button',
`i-button--type-${type.value}`,
])
const style = computed(() => [
{
width: 300,
fontSize: 16
}
])
return () => {
return (
<div class={cls.value} style={style.value}>按钮</div>
);
};
},
});
五、插槽
1、默认插槽
- React
- Vue
- Vue-JSX
import React from 'react';
const Button = (props) => {
const {
children,
} = props;
return (
<div>
{children}
</div>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<div>
<slot />
</div>
</template>
<style lang="scss">
@import './index.scss';
</style>
import { defineComponent } from 'vue';
import './index.scss';
export default defineComponent({
name: 'Button',
setup(props, { slots }) {
return () => {
const children = slots.default?.();
return (
<div>
{children}
</div>
);
};
},
});
2、具名插槽
2-1、定义
- React
- Vue
- Vue-JSX
import React from 'react';
const Button = (props) => {
const {
children,
prefixContent,
suffixContent
} = props;
return (
<div>
{prefixContent}
{children}
{suffixContent}
</div>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<div>
<slot name="prefixContent" />
<slot />
<slot name="suffixContent" />
</div>
</template>
<style lang="scss">
@import './index.scss';
</style>
import { defineComponent } from 'vue';
import './index.scss';
export default defineComponent({
name: 'Button',
setup(props, { slots }) {
return () => {
const prefixContent = slots.prefixContent?.();
const children = slots.default?.();
const suffixContent = slots.suffixContent?.();
return (
<div>
{prefixContent}
{children}
{suffixContent}
</div>
);
};
},
});
2-2、使用
- React
- Vue
- Vue-JSX
import React from 'react';
import Button from './Button.jsx'
import Icon from './Icon.jsx'
const App = () => {
const suffixIcon = <Icon name="Search" />;
return (
<Button
prefixContent="搜索"
suffixContent={suffixIcon}
>
按钮
</Button>
);
};
export default App;
<template>
<Button>
<template #prefixContent>
搜索
</template>
按钮
<template #suffixContent>
<Icon name="Search" />
</template>
</Button>
</template>
<script setup>
import Button from './button.vue'
import Icon from './icon.vue'
</script>
import { defineComponent } from 'vue';
import Button from './button.vue'
import Icon from './icon.vue'
export default defineComponent({
name: 'App',
setup(props) {
const suffixIcon = <Icon name="Search" />;
return () => {
return (
<Button>
// 注意这里默认插槽与具名插槽混用时需要写成以下形式 ↓
{{
default: () => '按钮',
prefixContent: () => '搜索',
suffixContent: () => suffixIcon
}}
</Button>
// 或
<Button
v-slots={{
prefixContent: () => '搜索',
suffixContent: () => suffixIcon
}}
>
按钮
</Button>
);
};
},
});
六、Props
React 中 interface
可从其它文件中引入,更为灵活,而 Vue 暂时只能在当前 .vue 文件中写,详情见 https://github.com/vuejs/core/issues/4294
- React
- Vue
- Vue-JSX
import React from 'react';
import classNames from 'classnames';
import './index.scss';
/**
* 这里的 ButtonProps 可以从 type.ts 引入
* import { ButtonProps } from './type.ts'
*/
interface ButtonProps {
/**
* 类名
*/
className?: string;
/**
* 自定义样式
*/
style?: React.CSSProperties;
/**
* 内容
*/
children?: React.ReactNode;
/**
* 是否禁用按钮
* @default false
*/
disabled?: boolean;
/**
* 按钮尺寸
* @default medium
*/
size?: 'small' | 'medium' | 'large';
}
const Button: React.FC<ButtonProps> = (props) => {
const {
children,
className,
style,
disabled = false,
size = 'medium',
...buttonProps
} = props;
return (
<button
className={classNames(
'i-button',
`i-button--size-${size}`,
className,
)}
style={{ ...style }}
disabled={disabled}
{...buttonProps}
>
{children}
</button>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<button
:class="[
'i-button',
`i-button--size-${size}`,
]"
>
<slot />
</button>
</template>
<script setup lang="ts">
/**
* 注意这里的 ButtonProps 无法从 type.ts 中引入
* 只能写在这个 .vue 文件中
* 详情见 https://github.com/vuejs/core/issues/4294
*/
interface ButtonProps {
/**
* 是否禁用按钮
* @default false
*/
disabled?: boolean
/**
* 按钮尺寸
* @default medium
*/
size?: 'small' | 'medium' | 'large'
}
const {
disabled = false,
size = 'medium',
} = defineProps<ButtonProps>()
</script>
<style lang="scss">
@import './index.scss';
</style>
import { defineComponent, PropType } from 'vue';
import './index.scss';
export default defineComponent({
name: 'Button',
props: {
/**
* 是否禁用按钮
* @default false
*/
disabled: {
type: Boolean,
default: false,
},
/**
* 按钮尺寸
* @default medium
*/
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium'
}
},
setup(props, { slots }) {
return () => {
const children = slots.default?.();
return (
<button
class={[
'i-button',
`i-button--size-${props.size}`,
]}
disabled={props.disabled}
>
{children}
</button>
);
};
},
});
七、Emit
- React
- Vue
- Vue-JSX
import React from 'react';
import classNames from 'classnames';
import './index.scss';
interface ButtonProps {
/**
* 类名
*/
className?: string;
/**
* 自定义样式
*/
style?: React.CSSProperties;
/**
* 内容
*/
children?: React.ReactNode;
/**
* 是否禁用按钮
* @default false
*/
disabled?: boolean;
/**
* 按钮尺寸
* @default medium
*/
size?: 'small' | 'medium' | 'large';
/**
* 点击事件
*/
onClick?: (e: React.MouseEvent) => void;
}
const Button: React.FC<ButtonProps> = (props) => {
const {
children,
className,
style,
disabled = false,
size = 'medium',
onClick,
...buttonProps
} = props;
return (
<button
className={classNames(
'i-button',
`i-button--size-${size}`,
className,
)}
style={{ ...style }}
disabled={disabled}
onClick={(e) => onClick?.(e)}
{...buttonProps}
>
{children}
</button>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<button
:class="['i-button', `i-button--size-${size}`]"
@click="(e) => emit('click', e)"
>
<slot />
</button>
</template>
<script setup lang="ts">
interface ButtonProps {
/**
* 是否禁用按钮
* @default false
*/
disabled?: boolean
/**
* 按钮尺寸
* @default medium
*/
size?: 'small' | 'medium' | 'large'
}
interface ButtonEmits {
/**
* 点击事件
*/
(type: 'click', e: MouseEvent): void
}
const {
disabled = false,
size = 'medium'
} = defineProps<ButtonProps>()
const emit = defineEmits<ButtonEmits>()
</script>
<style lang="scss">
@import './index.scss';
</style>
import { defineComponent, PropType } from 'vue';
import './index.scss';
export default defineComponent({
name: 'Button',
props: {
/**
* 是否禁用按钮
* @default false
*/
disabled: {
type: Boolean,
default: false,
},
/**
* 按钮尺寸
* @default medium
*/
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium'
}
},
emits: {
/**
* 点击事件
*/
'click': (e: MouseEvent) => true,
},
setup(props, { slots }) {
setup(props, { slots, emit }) {
return () => {
const children = slots.default?.();
return (
<button
class={[
'i-button',
`i-button--size-${props.size}`,
]}
disabled={props.disabled}
onClick={(e) => emit('click', e)}
>
{children}
</button>
);
};
},
});
八、给插槽传值
1、Provide 的方式
- React (父)
- React (子)
- Vue (父)
- Vue (子)
- Vue-JSX (父)
- Vue-JSX (子)
import React, { createContext } from 'react';
import Child from './Child.jsx'
export const MyContext = createContext(null as any);
const Parent = () => {
// React 这里的值变化时子组件拿到的也会同步更新
const ctx = {
txt: '传入的值'
}
return (
<MyContext.Provider value={ctx}>
<Child />
</MyContext.Provider>
);
};
export default Parent;
import React, { useContext } from 'react';
import { MyContext } from './Parent.tsx'
interface CtxType {
txt: string
}
const Child = () => {
const ctx: CtxType = useContext(MyContext)
return (
<div>{ctx.txt}</div>
);
};
export default Child;
<template>
<Child />
</template>
<script setup lang="ts">
import { provide } from 'vue'
import Child from './child.vue'
provide('MyContext', {
txt: '传入的值'
})
</script>
Vue provide
不是响应式的,如果要使其具备响应性,需要传入响应式数据,例如 ref
或 computed
等。
<template>
<div>{{ ctx.txt }}</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const ctx = inject('MyContext')
</script>
Vue provide
不是响应式的,如果要使其具备响应性,需要传入响应式数据,例如 ref
或 computed
等。
import { defineComponent, provide } from 'vue';
import Child from './child.vue'
export default defineComponent({
name: 'Parent',
setup(props) {
provide('MyContext', {
txt: '传入的值'
})
return () => {
return (
<Child />
);
};
},
});
Vue provide
不是响应式的,如果要使其具备响应性,需要传入响应式数据,例如 ref
或 computed
等。
import { defineComponent, inject } from 'vue';
export default defineComponent({
name: 'Child',
setup(props) {
const ctx = inject('MyContext')
return () => {
return (
<div>{ctx.txt}</div>
);
};
},
});
Vue provide
不是响应式的,如果要使其具备响应性,需要传入响应式数据,例如 ref
或 computed
等。
2、传入属性的方式
- React (父)
- React (子)
- Vue (父/子)
- Vue-JSX (父1)
- Vue-JSX (父2)
- Vue-JSX (子)
import React from 'react';
const Parent = (props) => {
const {
children
} = props
const childItems = React.Children.map(children, (child) => {
if (!React.isValidElement(child)) {
return null;
}
// React 这里的值变化时子组件拿到的也会同步更新
const childProps = {
txt: '传入的值',
...(child as React.ReactElement).props,
};
return React.cloneElement(child, childProps);
})
return (
<div>
{childItems}
</div>
);
};
export default Parent;
子组件自身传入的值是否会覆盖父组件传入的值,取决于父组件中对子组件 props 的赋值顺序,点击查看详情。
import React from 'react';
const Child = (props) => {
/**
* 这里传入的值是否会被父组件传入的覆盖掉
* 取决于父组件 childProps 中的赋值顺序
*/
const {
txt
} = props
return (
<div>{txt}</div>
);
};
export default Child;
.vue 在批量处理 slot
传参上不如 .tsx 便捷,可参考 h() 渲染函数 API 以及 cloneVNode() 克隆函数。
不过在组件封装中,还是用 .tsx,即 Vue-JSX,可读性更好且易于维护。
可使用 getAllElements
自定义函数,返回值用于模拟 React.Children,点击查看该函数详情。
// 方式一:使用 getAllElements 自定义函数(Arco 组件库中常用)
import { defineComponent, mergeProps } from 'vue';
import { getAllElements } from '../_utils/hook';
export default defineComponent({
name: 'Parent',
setup(props, { slots }) {
return () => {
const children = getAllElements(slots.default?.() ?? []);
const childItems = children.map((child, index) => {
// 这里的顺序决定父子组件属性的优先级谁高谁低
child.props = mergeProps(
child.props ?? {},
{ txt: '传入的值' } // 这里显然父组件传入的属性优先级更高
);
return child;
})
return (
<div>
{childItems}
</div>
);
};
},
});
也可使用 useChildComponentSlots
自定义函数,拿到指定的子组件,再进行遍历处理,点击查看该函数详情。
// 方式二:使用 useChildComponentSlots 自定义函数(TDesign 组件库中常用)
import { defineComponent } from 'vue';
import { useChildComponentSlots } from '../_utils/hook';
import Child from './Child.tsx'
export default defineComponent({
name: 'Parent',
setup(props) {
const childItems = () => {
const getChildComponentByName = useChildComponentSlots();
const childrenList = getChildComponentByName('Child')
const carouselItemList = childrenList.map((item: any) => {
return (
// 在这里决定父子组件属性的优先级谁高谁低
<Child txt={item.props?.txt ?? '传入的值'}>
{item.children.default()}
</Child>
);
});
return carouselItemList
}
return () => {
return (
<div>
{childItems()}
</div>
);
};
},
});
import { defineComponent, inject } from 'vue';
export default defineComponent({
name: 'Child',
props: {
txt: String
},
setup(props) {
return () => {
return (
<div>{props.txt}</div>
);
};
},
});
九、受控〡非受控
1、一般受控
一般情况下,组件通过传入 value
作为受控固定值,传入 defaultValue
作为非受控默认值;
其中 value
的值一旦固定,组件内部关联的变量将随之而固定,这就是所谓的受控;
而 defaultValue
即使固定不变,组件内部关联的变量也不受其影响,依然可以改变,这就是非受控。
- React
- React(引用的钩子)
- Vue
- Vue-JSX
import React from 'react';
import useDefault from '../hooks/useDefault';
interface SwitchProps {
/**
* 开关固定值(受控)
*/
value?: boolean;
/**
* 开关默认值(非受控)
* @default false
*/
defaultValue?: boolean;
/**
* 切换开关时触发
*/
onChange?: (value: boolean) => void;
}
const Switch: React.FC<SwitchProps> = (props) => {
const {
value,
defaultValue = false,
onChange,
...switchProps
} = props;
const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange);
const handleSwitch = () => {
// 注意这里不需要额外调用 onChange,钩子函数中已经做了处理
setInnerValue(!innerValue)
}
return (
<button
onClick={handleSwitch}
{...switchProps}
>
{innerValue ? '开' : '关'}
</button>
);
};
Switch.displayName = 'Switch';
export default Switch;
import { useState } from 'react';
export interface ChangeHandler<T, P extends any[]> {
// @ts-ignore
(value: T, ...args: P);
}
export default function useDefault<T, P extends any[]>(
value: T | undefined,
defaultValue: T,
onChange: ChangeHandler<T, P> | any,
): [T, ChangeHandler<T, P>] {
// 无论是否受控,都要 useState,因为 Hooks 是无条件的
const [internalValue, setInternalValue] = useState(defaultValue);
const defaultFn = () => {};
// 受控模式
if (typeof value !== 'undefined') {
return [value, onChange || defaultFn];
}
// 非受控模式
return [
internalValue,
(newValue, ...args) => {
setInternalValue(newValue);
if (typeof onChange === 'function') {
onChange(newValue, ...args);
}
},
];
}
<template>
<button @click="handleSwitch">
{{ innerValue ? '开' : '关' }}
</button>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
interface SwitchProps {
/**
* 开关固定值(受控)
*/
value?: boolean
/**
* 开关默认值(非受控)
* @default false
*/
defaultValue?: boolean
}
interface SwitchEmits {
/**
* 切换开关时触发
*/
(type: 'change', val: boolean): void
}
const {
value = undefined, // 注意这里传入的受控值需主动声明为 undefined
defaultValue = false
} = defineProps<SwitchProps>()
const emit = defineEmits<SwitchEmits>()
const _switchValue = ref(defaultValue)
const innerValue = computed(() => value ?? _switchValue.value)
const handleSwitch = () => {
const newVal = !innerValue.value
_switchValue.value = newVal
emit('change', newVal) // 这里需要主动调用外部传入的 change 函数
}
</script>
import { computed, defineComponent, ref } from 'vue';
export default defineComponent({
name: 'Switch',
props: {
/**
* 开关固定值(受控)
*/
value: {
type: Boolean,
default: undefined // 注意这里传入的受控值需主动声明为 undefined
},
/**
* 开关默认值(非受控)
* @default false
*/
defaultValue: {
type: Boolean,
default: false
}
},
emits: {
/**
* 切换开关时触发
*/
'change': (val: boolean) => true,
},
setup(props, { emit }) {
const _switchValue = ref(props.defaultValue)
const innerValue = computed(() => props.value ?? _switchValue.value)
const handleSwitch = () => {
const newVal = !innerValue.value
_switchValue.value = newVal
emit('change', newVal) // 这里需要主动调用外部传入的 change 函数
}
return () => {
return (
<button
onClick={handleSwitch}
>
{innerValue.value ? '开' : '关'}
</button>
);
};
},
});
2、input 受控
在 React 中,input
/ textarea
设置了 value 属性则为受控组件,可通过 onChange 事件配合 setState()
改变 value
值来更新输入框的值。
而在 Vue 中,input
/ textarea
设置 value 值,当 value 发生变化时,输入框中的值并不会跟着变化,而且即使 value 属性定死,输入框仍可以自由输入。另外,Vue 中 onInput 事件相当于 React 的 onChange 事件。
Vue 的 input 受控需要在 onInput 中通过 nextTick 单独处理。
- React
- React(引用的钩子)
- Vue
- Vue-JSX
import React from 'react';
import useDefault from '../hooks/useDefault';
interface InputProps {
/**
* 输入框固定值(受控)
*/
value?: string | number;
/**
* 输入框默认值(非受控)
* @default ''
*/
defaultValue?: string | number;
/**
* 输入框输入时触发
*/
onChange?: (value: string | number, ev: React.ChangeEvent<HTMLInputElement>) => void;
}
const Input: React.FC<InputProps> = (props) => {
const {
value,
defaultValue = '',
onChange,
...inputProps
} = props;
const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange);
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
// 注意这里不需要额外调用 onChange,钩子函数中已经做了处理
setInnerValue(e.target.value, e)
}
return (
<input
value={innerValue}
onChange={handleInput}
{...inputProps}
/>
);
};
Input.displayName = 'Input';
export default Input;
import { useState } from 'react';
export interface ChangeHandler<T, P extends any[]> {
// @ts-ignore
(value: T, ...args: P);
}
export default function useDefault<T, P extends any[]>(
value: T | undefined,
defaultValue: T,
onChange: ChangeHandler<T, P> | any,
): [T, ChangeHandler<T, P>] {
// 无论是否受控,都要 useState,因为 Hooks 是无条件的
const [internalValue, setInternalValue] = useState(defaultValue);
const defaultFn = () => {};
// 受控模式
if (typeof value !== 'undefined') {
return [value, onChange || defaultFn];
}
// 非受控模式
return [
internalValue,
(newValue, ...args) => {
setInternalValue(newValue);
if (typeof onChange === 'function') {
onChange(newValue, ...args);
}
},
];
}
<template>
<input
ref="inputRef"
:value="innerValue"
@input="handleInput"
/>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
interface InputProps {
/**
* 输入框固定值(受控)
*/
value?: string | number
/**
* 输入框默认值(非受控)
* @default false
*/
defaultValue?: string | number
}
interface InputEmits {
/**
* 输入框输入时触发
*/
(type: 'input', val: string | number, ev: Event): void
}
const {
value = undefined, // 注意这里传入的受控值需主动声明为 undefined
defaultValue = ''
} = defineProps<InputProps>()
const emit = defineEmits<InputEmits>()
const _inputValue = ref(defaultValue)
const innerValue = computed(() => value ?? _inputValue.value)
const inputRef = ref<HTMLInputElement>()
const handleInput = (e: Event) => {
const newVal = (e.target as HTMLInputElement).value
_inputValue.value = newVal
emit('input', newVal, e)
// 受控处理
nextTick(() => {
if (inputRef.value && innerValue.value !== inputRef.value.value) {
inputRef.value.value = innerValue.value as string
}
})
}
</script>
import { computed, defineComponent, ref, nextTick } from 'vue';
export default defineComponent({
name: 'Input',
props: {
/**
* 输入框固定值(受控)
*/
value: {
type: [String, Number],
default: undefined // 注意这里传入的受控值需主动声明为 undefined
},
/**
* 输入框默认值(非受控)
* @default ''
*/
defaultValue: {
type: [String, Number],
default: ''
}
},
emits: {
/**
* 输入框输入时触发
*/
'input': (val: string | number, ev: Event) => true,
},
setup(props, { emit }) {
const _inputValue = ref(props.defaultValue)
const innerValue = computed(() => props.value ?? _inputValue.value)
const inputRef = ref<HTMLInputElement>()
const handleInput = (e: Event) => {
const newVal = (e.target as HTMLInputElement).value
_inputValue.value = newVal
emit('input', newVal, e)
// 受控处理
nextTick(() => {
if (inputRef.value && innerValue.value !== inputRef.value.value) {
inputRef.value.value = innerValue.value as string
}
})
}
return () => {
return (
<input
ref={inputRef}
value={innerValue.value}
onInput={handleInput}
/>
);
};
},
});
十、挂载到指定节点
React 通过 ReactDOM.createPortal 来挂载到指定节点,而 Vue 通过 Teleport 来实现。
- React
- Vue
- Vue-JSX
import React from 'react';
import ReactDOM from 'react-dom';
const Button = (props) => {
const wrapper = document.querySelector('#wrapper');
return (
<div>
{ReactDOM.createPortal(
<button>按钮</button>,
wrapper,
)}
</div>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<div>
<Teleport to="#wrapper">
<button>按钮</button>
</Teleport>
</div>
</template>
import { defineComponent, Teleport } from 'vue';
export default defineComponent({
name: 'Button',
setup() {
return () => {
return (
<div>
<Teleport to="#wrapper">
<button>按钮</button>
</Teleport>
</div>
);
};
},
});
十一、通过属性传入节点
React 可直接通过属性的方式传入任意节点,而 Vue 需要提供 jsx 的语法来渲染作为属性传入的自定义节点,如果要使用 Vue 模板语法,需要单独封装一个 VNode 组件来对传入的自定义属性节点进行处理并渲染。
- React (父)
- React (子)
- Vue (父)
- Vue (子)
- 封装的 VNode
- Vue-JSX (子)
import React from 'react';
import Child from './Child.jsx'
const App = () => {
const a = <span>按钮</span>
return (
<Child content={a} />
);
};
export default App;
import React from 'react';
const Button = (props) => {
const {
content
} = props
return (
<div>{content}</div>
);
};
Button.displayName = 'Button';
export default Button;
<template>
<div>
<Child :content="a" />
</div>
</template>
<script setup>
import { h } from 'vue'
import Child from './Child.vue'
const a = h('span', '按钮')
</script>
Vue 的模板语法无法直接通过 {{}}
的方式传入 VNode
,需要单独封装一个 VNode 组件来进行处理:
<template>
<div>
<VNode :content="content" />
</div>
</template>
<script setup lang="ts">
import { VNodeTypes } from 'vue';
import VNode from './v-node';
type VueNode = string | symbol | VNodeTypes;
interface ButtonProps {
content?: VueNode;
}
const { content } = defineProps<ButtonProps>();
</script>
import { defineComponent, PropType, VNodeTypes } from 'vue';
export default defineComponent({
name: 'VNode',
props: {
/**
* 任意节点内容,类似 ReactNode
*/
content: {
type: [String, Symbol, Object] as PropType<string | symbol | VNodeTypes>
},
},
setup(props) {
const render = () => {
return (
<>
{props.content}
</>
);
};
return {
render
}
},
render() {
return this.render();
},
});
import { defineComponent, PropType, VNodeTypes } from 'vue';
type VueNode = string | symbol | VNodeTypes;
export default defineComponent({
name: 'Child',
props: {
content: [String, Symbol, Object] as PropType<VueNode>,
},
setup(props) {
return () => {
return <div>{props.content}</div>;
};
},
});