React的图形界面其实就是通过一层层的组件构建而成的。本篇笔记我们介绍React中组件的概念,以及组件状态和生命周期相关的概念和用法。
在早期版本React曾经广泛使用class
定义组件,但这种方式在最新的版本中已经弃用了,取而代之的是函数式组件和Hooks,本系列笔记的后续内容也将使用这种代码风格进行介绍。
下面例子代码中,我们创建了一个最简单的组件<App />
并将其渲染到DOM中。
import React from "react";
import ReactDOM from "react-dom/client";
const App = () => {
return <div>Hello, React!</div>;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
代码中,App
就是一个函数式组件,它的功能比较简单,它没接收任何参数,只是输出一些HTML内容。虽然这里App
本质上是一个函数,但React要求作为组件名必须以大写字母开头,即PascalCase命名,否则可能无法正确渲染。这里还要注意的是组件返回的HTML必须是树形的,即必须存在一个根节点,如果不希望使用<div></div>
包裹,也可以用空标签<>
。
熟悉JavaScript语言的同学可能发现上面代码的写法并不符合JavaScript标准,return
的返回值居然是一段HTML代码,这其实是JSX的用法,JSX你可以将其理解为一种JavaScript的语法糖,在React中构建图形界面都是使用JSX来输出内容的。
随后,我们调用了ReactDOM.createRoot()
方法,它将React组件挂载到了HTML中的真实DOM节点上,后续就会以这个节点挂载的组件为根组件进行内容的渲染,最后我们调用root.render()
将App
组件渲染到了根节点上。
一些额外说明:
ReactDom.render()
方法,该方法已经弃用。React.StrictMode
包裹了我们的根组件,这是新版本React提供的严格模式功能,严格模式能够进行额外的检查并输出一些警告信息,对我们开发阶段很有帮助,建议开启。React的图形界面就是通过组件一层层嵌套构成的,它是一个树形结构。引用子组件非常简单,我们直接在父组件JSX中像使用HTML标签一样引用子组件即可:
return (
<div>
<ChildComponent></ChildComponent>
</div>
);
React组件的状态数据分为两种:props
和state
,这些状态数据的改变都会引发组件的重新渲染。两者的区别是:props
是组件对外的数据接口,用于将数据从父组件传递给子组件;而state
用于维护组件内部的状态数据。
state
用于维护组件内部的状态数据,下面例子中,我们的组件内部维护一个数字变量,点击按钮时数字加1。
import { useState } from "react";
const App = () => {
const [cnt, setCnt] = useState(0);
const incrementCnt = () => {
setCnt(cnt + 1);
};
return (
<div>
<button onClick={incrementCnt}>ClickMe</button>
{cnt}
</div>
);
};
export default App;
我们这里定义了App
组件,和之前不同的是这里我们使用了useState()
这个Hooks函数,它用于创建组件状态。useState()
的返回值比较特殊,它返回有2个元素的数组,第1个元素用于读取当前状态值,第2个元素是一个Set函数,用于设置状态值。代码中的incrementCnt()
是我们自定义的函数,它用于作为按钮点击的回调函数,每次触发就会将cnt
的值加1。最后,在返回的JSX中,我们关联了按钮的点击和incrementCnt()
函数,并将cnt
的值显示出来。
注意:
state
值必须通过Set函数,直接修改值是不会触发组件函数重新执行的。useEffect()
对状态数据进行监听。有关更多Hooks函数的使用我们会在后续章节用到时详细介绍。
props
是组件对外的数据接口,用于将数据从父组件传递给子组件。下面例子中,我们将数据从ParentComponent
传到ChildComponent
。
ParentComponent.jsx
import ChildComponent from "./ChildComponent";
const ParentComponent = () => {
return <ChildComponent text="hello"></ChildComponent>;
};
export default ParentComponent;
ChildComponent.jsx
const ChildComponent = (props) => {
return <div>{props.text}</div>;
};
export default ChildComponent;
代码中,我们的父组件ParentComponent
引用了子组件ChildComponent
,并通过props
向子组件传递了参数text="hello"
,子组件中我们可以通过props.text
进行读取。
这里要注意2个规则:
props
值时,会触发子组件的重新渲染props
在子组件中是只读的,不可以改变,但你可以将props
作为初始值再赋予一个state
React中,组件的生命周期主要分为3部分:
装载(mount):组件第一次在DOM树中渲染
更新(update):组件被重新渲染
卸载(unmount):组件从DOM树中删除
在老版本的ClassComponent中,React提供了许多生命周期回调函数用于处理这些生命周期,但在新版本的函数式组件中,取消了这些回调函数,但我们依然可以通过useEffect()
来实现同样的功能。
import { useEffect } from "react";
const App = () => {
useEffect(() => {
console.log("mount & update");
}, []);
return <div></div>;
};
export default App;
上面代码运行时,我们可以看到组件在加载后输出相关的信息。