3、React组件基础:函数组件与类组件、组件的props(属性传递)、组件的children、组件组合模式
好,咱们今天来聊聊React组件的核心概念。
说实话,我刚接触React那会儿,最困惑的就是「组件到底是个啥」。后来我明白了——组件就是页面上的一块积木。你搭积木,我搭积木,大家拼在一起就成了一个完整的页面。
3.1 函数组件 vs 类组件
React里写组件,主要有两种方式。我个人习惯用函数组件,但类组件你也要认识,毕竟老项目里到处都是。
函数组件
说白了,就是一个普通的JavaScript函数。它接收一个参数(我们叫它props),然后返回一段JSX。
// 最简单的函数组件
function Welcome(props) {
return <h1>你好,{props.name}</h1>;
}
// 或者用箭头函数
const Welcome = (props) => {
return <h1>你好,{props.name}</h1>;
};
嗯,这里要注意:函数组件的名字首字母必须大写。不然React会把它当成普通HTML标签处理。
类组件
类组件是用ES6的class写的。它必须继承React.Component,而且里面要有一个render方法。
class Welcome extends React.Component {
render() {
return <h1>你好,{this.props.name}</h1>;
}
}
我在项目中遇到过不少老代码,全是类组件。那时候还没有hooks,大家只能用class。现在新项目我建议都用函数组件,代码更简洁。
- 函数组件:简单、轻量、推荐使用
- 类组件:功能完整、有生命周期、适合老项目维护
3.2 组件的props(属性传递)
props就是父组件传给子组件的数据。你想想看,一个组件如果啥数据都不接收,那它就是个死组件,没法复用。
// 父组件
function App() {
return (
<div>
<UserCard name="张三" age={25} isVIP={true} />
<UserCard name="李四" age={30} isVIP={false} />
</div>
);
}
// 子组件
function UserCard(props) {
return (
<div className="card">
<h3>{props.name}</h3>
<p>年龄:{props.age}</p>
<p>{props.isVIP ? 'VIP用户' : '普通用户'}</p>
</div>
);
}
这里有几个坑,我曾经踩过:
- props是只读的!千万别在子组件里修改props
- 传数字要用花括号:age={25},不是age="25"
- 传布尔值也一样:isVIP={true}
为什么会这样?因为React是单向数据流。数据从父组件流向子组件,子组件只能读,不能改。你要是改了,React会报错。
3.3 组件的children
children是个特殊的东西。它代表组件标签之间的内容。
// 使用children
function Card(props) {
return (
<div className="card">
{props.children}
</div>
);
}
// 调用时
<Card>
<h2>标题</h2>
<p>这是一段描述文字</p>
</Card>
你看,Card组件不知道里面会放什么。它只是把children渲染出来。这就很灵活了。
我个人习惯用children来做布局组件。比如一个弹窗组件,内容千变万化,用children最合适。
children可以是任何东西——字符串、JSX、甚至是一个函数。我做过一个项目,用children传了一个回调函数进去,实现了「渲染劫持」的效果。
3.4 组件组合模式
组合模式,说白了就是「把组件拼在一起」。这是React最强大的设计思想之一。
3.4.1 容器组件 + 展示组件
// 容器组件:负责数据逻辑
function UserListContainer() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then(data => setUsers(data));
}, []);
return <UserList users={users} />;
}
// 展示组件:只负责渲染
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
这种模式的好处是:逻辑和UI分离。你改样式不会影响数据,改数据不会影响样式。
3.4.2 复合组件模式
// 一个Select组件,由多个子组件组成
function Select(props) {
return <select>{props.children}</select>;
}
Select.Option = function Option(props) {
return <option value={props.value}>{props.children}</option>;
};
// 使用
<Select>
<Select.Option value="1">选项一</Select.Option>
<Select.Option value="2">选项二</Select.Option>
</Select>
我在项目中遇到过这种场景:一个复杂的表单组件,里面有输入框、下拉框、日期选择器。用复合组件模式,调用方可以自由组合,非常灵活。
3.4.3 高阶组件(HOC)
高阶组件是个函数,它接收一个组件,返回一个新组件。说白了就是「给组件加料」。
function withLogging(WrappedComponent) {
return function(props) {
console.log('组件渲染了');
return <WrappedComponent {...props} />;
};
}
const EnhancedButton = withLogging(Button);
- 小组件拼成小组件
- 小组件拼成大组件
- 大组件拼成页面
- 页面拼成应用
你想想看,这种模式的好处是什么?每个组件只关心自己的事。出了问题,定位也快。改代码也不怕影响其他地方。
嗯,最后说一句。组件组合不是银弹。有时候过度拆分反而让代码变得复杂。我的经验是:一个组件如果超过200行,或者它做的事情太多,就该考虑拆分了。
好了,这一章的内容就到这里。下一章咱们聊聊React的状态管理——useState和useReducer,这可是React开发的重头戏。