Skip to main content

Vue〡React 写法对照指南

一、状态值

index.jsx
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

以下对照指南以 React HookVue-CompositionVue-JSX 的写法为例。

二、显示隐藏

<>
{show && <Button>按钮</Button>}
{show ? <Button>显示</Button> : <Button>隐藏</Button>}
</>

三、属性传值

<Button type="primary">按钮</Button>
<Button disabled>按钮</Button>
<Button loading={true}>按钮</Button>
<Button name={getName()}>按钮</Button>
<Button onClick={handleAdd}>按钮</Button>

四、类名 & 样式

1、内联写法

<div
className={classNames(
'i-button',
`i-button--type-${type}`,
className,
)}
>
按钮
</div>
<div
style={{
width: 300,
fontSize: 16
}}
>
按钮
</div>

2、分离写法

index.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;

五、插槽

1、默认插槽

index.jsx
import React from 'react';

const Button = (props) => {
const {
children,
} = props;

return (
<div>
{children}
</div>
);
};

Button.displayName = 'Button';

export default Button;

2、具名插槽

2-1、定义

Button.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;

2-2、使用

index.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;

六、Props

注意

React 中 interface 可从其它文件中引入,更为灵活,而 Vue 暂时只能在当前 .vue 文件中写,详情见 https://github.com/vuejs/core/issues/4294

index.tsx
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;

七、Emit

index.tsx
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;

八、给插槽传值

1、Provide 的方式

Parent.tsx
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;

2、传入属性的方式

Parent.tsx
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;

九、受控〡非受控

1、一般受控

说明

一般情况下,组件通过传入 value 作为受控固定值,传入 defaultValue 作为非受控默认值;

其中 value 的值一旦固定,组件内部关联的变量将随之而固定,这就是所谓的受控;

defaultValue 即使固定不变,组件内部关联的变量也不受其影响,依然可以改变,这就是非受控。

index.tsx
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;

2、input 受控

说明

在 React 中,input / textarea 设置了 value 属性则为受控组件,可通过 onChange 事件配合 setState() 改变 value 值来更新输入框的值。

而在 Vue 中,input / textarea 设置 value 值,当 value 发生变化时,输入框中的值并不会跟着变化,而且即使 value 属性定死,输入框仍可以自由输入。另外,Vue 中 onInput 事件相当于 React 的 onChange 事件。

Vue 的 input 受控需要在 onInput 中通过 nextTick 单独处理。

index.tsx
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;

十、挂载到指定节点

React 通过 ReactDOM.createPortal 来挂载到指定节点,而 Vue 通过 Teleport 来实现。

index.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;

十一、通过属性传入节点

说明

React 可直接通过属性的方式传入任意节点,而 Vue 需要提供 jsx 的语法来渲染作为属性传入的自定义节点,如果要使用 Vue 模板语法,需要单独封装一个 VNode 组件来对传入的自定义属性节点进行处理并渲染。

index.jsx
import React from 'react';
import Child from './Child.jsx'

const App = () => {
const a = <span>按钮</span>
return (
<Child content={a} />
);
};

export default App;