作为 Vue 开发者,在学习 React 的过程中,除了语法和状态管理的差异,组件设计模式的差异也是一个重要的方面。本文将从 Vue 开发者熟悉的角度出发,详细介绍 React 中常用的组件设计模式。
组件基础对比
Vue 组件
Vue 组件通常采用单文件组件(SFC)的形式:
<!-- UserCard.vue -->
<template>
<div class="user-card">
<img :src="avatar" :alt="username" src="">
<h3>{{ username }}</h3>
<p>{{ bio }}</p>
<slot name="actions"></slot>
</div>
</template>
<script>
export default {
name: 'UserCard',
props: {
username: {
type: String,
required: true
},
avatar: {
type: String,
default: '/default-avatar.png'
},
bio: String
}
}
</script>
<style scoped>
.user-card {
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
}
</style>
React 组件
React 组件通常采用函数组件的形式:
// UserCard.jsx
import React from 'react';
import PropTypes from 'prop-types';
import './UserCard.css';
function UserCard({ username, avatar = '/default-avatar.png', bio, children }) {
return (
<div classname="user-card">
<img src="{avatar}" alt="{username}">
<h3>{username}</h3>
{bio && <p>{bio}</p>}
{children}
</div>
);
}
UserCard.propTypes = {
username: PropTypes.string.isRequired,
avatar: PropTypes.string,
bio: PropTypes.string,
children: PropTypes.node
};
export default UserCard;
主要区别:
- 文件组织方式不同
- Vue 使用单文件组件,将模板、逻辑和样式放在一起
- React 将组件和样式分离,使用 JSX 内联模板
- 属性验证方式不同
- Vue 使用 props 选项
- React 使用 PropTypes(可选)
- 插槽实现方式不同
- Vue 使用具名插槽
- React 使用 children 属性
组件组合模式
1. 高阶组件(HOC)
在 Vue 中,我们通常使用 mixins 或组合式 API 来复用组件逻辑:
// Vue Mixin
const withLogger = {
created() {
console.log(`${this.$options.name} 组件已创建`);
},
mounted() {
console.log(`${this.$options.name} 组件已挂载`);
}
};
export default {
name: 'MyComponent',
mixins: [withLogger]
};
在 React 中,我们使用高阶组件:
// withLogger.js
function withLogger(WrappedComponent) {
return function WithLoggerComponent(props) {
React.useEffect(() => {
console.log(`${WrappedComponent.name} 组件已挂载`);
return () => {
console.log(`${WrappedComponent.name} 组件将卸载`);
};
}, []);
return <wrappedcomponent {...props} />;
};
}
// 使用高阶组件
const EnhancedComponent = withLogger(MyComponent);
2. 自定义 Hooks
Vue 3 的组合式 API:
// useCounter.js
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return {
count,
increment,
decrement
};
}
// 使用组合式函数
export default {
setup() {
const { count, increment, decrement } = useCounter();
return {
count,
increment,
decrement
};
}
};
React 的自定义 Hooks:
// useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return {
count,
increment,
decrement
};
}
// 使用自定义 Hook
function Counter() {
const { count, increment, decrement } = useCounter();
return (
<div>
<p>{count}</p>
<button onclick="{increment}">+1</button>
<button onclick="{decrement}">-1</button>
</div>
);
}
3. 复合组件模式
Vue 的具名插槽:
<!-- Tabs.vue -->
<template>
<div class="tabs">
<div class="tabs-nav">
<slot name="nav"></slot>
</div>
<div class="tabs-content">
<slot></slot>
</div>
</div>
</template>
<!-- 使用组件 -->
<template>
<tabs>
<template #nav>
<button>标签1</button>
<button>标签2</button>
</template>
<div>内容1</div>
<div>内容2</div>
</tabs>
</template>
React 的复合组件:
// Tabs.jsx
const TabsContext = React.createContext();
function Tabs({ children, defaultActiveKey }) {
const [activeKey, setActiveKey] = useState(defaultActiveKey);
return (
<tabscontext.provider value="{{" activekey, setactivekey }}>
<div classname="tabs">{children}</div>
</tabscontext.provider>
);
}
function TabNav({ children }) {
return <div classname="tabs-nav">{children}</div>;
}
function TabContent({ children }) {
return <div classname="tabs-content">{children}</div>;
}
function TabPane({ children, tabKey }) {
const { activeKey } = useContext(TabsContext);
if (tabKey !== activeKey) return null;
return children;
}
// 组合使用
function App() {
return (
<tabs defaultactivekey="1">
<tabnav>
<button>标签1</button>
<button>标签2</button>
</tabnav>
<tabcontent>
<tabpane tabkey="1">内容1</tabpane>
<tabpane tabkey="2">内容2</tabpane>
</tabcontent>
</tabs>
);
}
实战示例:表单组件
让我们通过一个表单组件的例子来实践这些模式:
// useForm.js
function useForm(initialValues = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = (name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
};
const handleBlur = (name) => {
setTouched(prev => ({ ...prev, [name]: true }));
};
const validate = (validationSchema) => {
const newErrors = {};
Object.keys(validationSchema).forEach(field => {
const value = values[field];
const rules = validationSchema[field];
if (rules.required && !value) {
newErrors[field] = '此字段是必填的';
} else if (rules.pattern && !rules.pattern.test(value)) {
newErrors[field] = '格式不正确';
}
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return {
values,
errors,
touched,
handleChange,
handleBlur,
validate
};
}
// Form.jsx
const FormContext = React.createContext();
function Form({ children, initialValues, validationSchema, onSubmit }) {
const form = useForm(initialValues);
const handleSubmit = (e) => {
e.preventDefault();
if (form.validate(validationSchema)) {
onSubmit(form.values);
}
};
return (
<formcontext.provider value="{form}">
<form onSubmit="{handleSubmit}">
{children}
</form>
</formcontext.provider>
);
}
// FormField.jsx
function FormField({ name, label, type = 'text' }) {
const { values, errors, touched, handleChange, handleBlur } = useContext(FormContext);
const hasError = touched[name] && errors[name];
return (
<div classname="form-field">
<label>{label}</label>
<input type="{type}" value="{values[name]" || ''} onChange="{e" => handleChange(name, e.target.value)}
onBlur={() => handleBlur(name)}
className={hasError ? 'error' : ''}
/>
{hasError && <span classname="error-message">{errors[name]}</span>}
</div>
);
}
// 使用示例
function RegistrationForm() {
const handleSubmit = (values) => {
console.log('提交的数据:', values);
};
const validationSchema = {
username: {
required: true
},
email: {
required: true,
pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
},
password: {
required: true,
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/
}
};
return (
<form initialValues="{{" username: '', email: password: '' }} validationSchema="{validationSchema}" onSubmit="{handleSubmit}">
<formfield name="username" label="用户名" />
<formfield name="email" label="邮箱" type="email" />
<formfield name="password" label="密码" type="password" />
<button type="submit">注册</button>
</form>
);
}
性能优化模式
1. 组件记忆化
Vue 的 keep-alive
:
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
React 的 memo
:
const MemoizedComponent = React.memo(function MyComponent(props) {
return (
// 组件实现
);
}, (prevProps, nextProps) => {
// 返回 true 如果你不希望组件更新
return prevProps.id === nextProps.id;
});
2. 懒加载模式
Vue 的异步组件:
const AsyncComponent = () => ({
component: import('./MyComponent.vue'),
loading: LoadingComponent,
error: ErrorComponent,
delay: 200,
timeout: 3000
});
React 的懒加载:
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<suspense fallback="{<Loading" />}>
<lazycomponent />
);
}
3. 虚拟列表
function VirtualList({ items, itemHeight, windowHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(windowHeight / itemHeight),
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const totalHeight = items.length * itemHeight;
const offsetY = startIndex * itemHeight;
return (
<div style="{{" height: windowheight, overflow: 'auto' }} onscroll="{e" => setScrollTop(e.target.scrollTop)}
>
<div style="{{" height: totalheight, position: 'relative' }}>
<div style="{{" transform: `translatey(${offsety}px)` }}>
{visibleItems.map(item => (
<div key="{item.id}" style="{{" height: itemheight }}>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
最佳实践
-
组件设计原则
- 单一职责
- 组件接口明确
- 保持组件纯函数特性
- 合理使用 Props 和 State
-
代码组织
- 按功能组织文件
- 组件拆分适度
- 复用逻辑抽象
- 保持一致的命名规范
-
性能优化
- 合理使用记忆化
- 避免不必要的渲染
- 使用懒加载
- 实现虚拟滚动
小结
-
React 组件设计的特点:
- 函数式编程
- 组合优于继承
- 单向数据流
- 声明式开发
-
从 Vue 到 React 的转变:
- 告别选项式 API
- 拥抱 Hooks
- 组件组合方式
- 性能优化思路
-
开发建议:
- 理解设计模式
- 掌握最佳实践
- 注重代码质量
- 持续学习进步
下一篇文章,我们将深入探讨 React 的路由和导航管理,帮助你构建完整的单页应用。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍