组件属性和状态
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即
props
),并返回用于描述页面展示内容的 React 元素。组件允许我们将
UI
拆分成独立可复用的代码片段,并对每个片段进行独立构思。
组件&属性
函数组件
定义组件最简单的方式就是编写 JavaScript 函数:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
该函数是一个有效的 React 组件,因为它接收唯一带有数据的 props
(代表属性)对象并返回一个 React 元素。这类组件被称为函数组件,因为它本质上就是 JavaScript 函数。
渲染组件
在此之前,我们遇到的 React 元素都只是 DOM 标签:
const element = <div />;
不过,React 元素也可以是用户自定义的组件:
const element = <Welcome name="张先生" />;
当 React 元素为用户自定义组件时,它会将JSX
所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 props
,即属性。
例如,下面的这段代码会在页面上渲染"Hello,张先生":
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="张先生" />;
ReactDOM.render(element, document.getElementById('root'));
上述示例中,主要有以下几点需要关注:
- 我们调用
ReactDOM.render()
函数,并传入<Welcome name="张先生" />
作为参数。 - React 调用
Welcome
组件,并将{name: '张先生'}
作为 props 传入。 Welcome
组件将<h1>Hello, 张先生</h1>
元素作为返回值。- React DOM 将 DOM 高效地更新为
<h1>Hello, 张先生</h1>
。
注意:组件名称必须采用大写字母开头的驼峰式
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,
<div />
代表 HTML 的 div 标签,而<Welcome />
则代表一个组件,并且需在作用域内使用Welcome
。
组合组件
组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。
例如,我们可以创建一个可以多次渲染 Welcome
组件的 App
组件:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="张先生" />
<Welcome name="王女士" />
<Welcome name="孙先生" />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
通常来说,每个新的 React 应用程序的顶层组件都是 App
组件。但是,如果你将 React 集成到现有的应用程序中,你可能需要使用像 Button
这样的小组件,并自下而上地将这类组件逐步应用到视图层的每一处。
提取组件
将组件拆分为更小的组件。
我们将以Comment
组件为例进行拆分:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img
className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">{props.author.name}</div>
</div>
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}
该组件用于描述一个社交媒体网站上的评论功能,它接收 author
(对象),text
(字符串)以及 date
(日期)作为 props。
该组件由于嵌套关系比较多,变得难以维护,并且我们无法复用它的各个部分,所以我们决定对其进行拆分处理。
Avatar.js
function Avatar(props) {
return (
<img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />
);
}
Avatar
不需知道它在 Comment
组件内部是如何渲染的。因此,我们给它的 props 起了一个更通用的名字:user
,而不是 author
。
UserInfo
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">{props.user.name}</div>
</div>
);
}
该组件的作用是在用户名旁渲染 Avatar
组件。
提取组件之后的Comment
组件:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}
开始之前,我们可能会觉得提取组件是一件繁重的工作,但是,在大型应用中,构建可复用组件库是完全值得的。如果 UI
中有一部分被多次使用(Button
,Panel
,Avatar
),或者组件本身就足够复杂(App
,FeedStory
,Comment
),那么它就是一个可复用组件的候选项。
属性只读性
属性是只读的,无论在什么场景下,都不能直接修改自身属性。
function sum(a, b) {
return a + b;
}
这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
相反,下面这个函数则不是纯函数,因为它更改了自己的入参:
function withdraw(account, amount) {
account.total -= amount;
}
React 非常灵活,但它也有一个严格的规则:
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
状态
React 把组件看成一个状态机,通过与用户的交互,实现不同状态,然后渲染 UI
,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
State 与 Props 的区别
- State 是可变的,是组件内部维护的一组用于反映组件 UI 变化的状态集合;
- Props 对于使用它的组件来说,是只读的,要想修改 Props,只能通过该组件的父组件修改。
下面是一个可复用的Clock
组件:
function Clock(props) {
const defaultDate = new Date();
const [date, setDate] = useState(defaultDate);
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {date.toLocaleTimeString()}.</h2>
</div>
);
}
ReactDOM.render(<Clock />, document.getElementById('root'));
上述示例中,date
就是一个state
值,组件会根据date
值的变化,同步渲染UI
。
正确使用 state
不能直接修改 state
以上面的Colock
组件为例,它的date
如果按照下面的方式写会出现编译错误:
// Wrong
date = new Date();
我们应该使用正确的方式为其赋值:
// Correct
setDate(new Date());
数据是向下流动的
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的。
这就是为什么称state
为局部的或封装的的原因。除了拥有并设置了它的组件本身,其它组件都无法访问。
组件可以选择把它的state
作为props
传递给它的子组件,这通常被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
为了证明每个组件都是真正独立的,我们可以创建一个渲染三个 Clock
的 App
组件:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
上述示例中每个 Clock
组件都会单独设置它自己的计时器并且更新它。
总结
通过此篇文章,希望我们能掌握组件的概念,以及组件类型、组件渲染、组件拆分等基本理论,并且能够熟练区分组件的属性和状态,正确处理组件交互。