React组件的概念
一个react 工程,就是由无数个组件组成的,react 所作的就是将组件绘制到 web 页面上。
嵌套组件、组合组件,重用组件,像拼积木一样来构成页面。
一个合格的react工程师要会绘制组件树。
对于函数式组件,一个满足条件的函数就是一个React组件。
定义组件
组件概述
React 允许你将标签、CSS 和 JavaScript 组合成自定义“组件”,即 应用程序中可复用的 UI 元素。
React 组件是一段可以 使用标签进行扩展 的 JavaScript 函数。
例如:
export default function App(){
return (
<h1>hello world!!</h1>
)
}
构建组件的步骤
1.导出组件
要使用React组件,需要将该组件导出,导出组件支持默认导出和具名导出。
默认导出
export default function App(){
return (
<h1>hello world!!</h1>
)
}
使用时进行导入
导入导出采用的是正常js导入导出规则,例如
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
//如果后缀为js、jsx、ts、tsx可以省略后缀
import App from './App'
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App/>
</StrictMode>
);
2.定义函数
React 组件是常规的 JavaScript 函数,但 组件的名称必须以大写字母开头,否则它们将无法运行!
例如,我们定一个Book组件,
export default function Book(){
...
}
3.添加标签
React函数式组件必须有返回值,该值的类型为JSX.Element|null.
如果我们组件内容为空,就返回null,例如
export default function Empty(){
return null
}
否则我们需要返回一个jsx标签,jsx标签就是一个html片段中混入js代码。
export default function Book(){
let str = "Hello world!!"
//jsx中的js代码用{}包裹
return <h1>{str}</h1>
}
但是,如果你的标签和 return 关键字不在同一行,则必须把它包裹在一对括号中,如下所示:
export default function Book(){
return (
<div>
<h1>hello world!!</h1>
</div>
)
}
没有括号包裹的话,任何在
return下一行的代码都将被忽略!
组件规则
1.函数组件的首字母大写
//普通函数
function add(){
return a+b;
}
//函数组件
function App(){
//一定要返回html结构
//null表示组件为空
return null
}
1.为了和普通函数相区分,React源码在解析React工程时会将首字母大写的函数识别为React组件。
2.React组件由于要绘制到页面上,所以必须返回一个jsx结构,null代表渲染为空。
2.不支持嵌套声明
函数组件内部可以声明普通函数,但不应该嵌套声明其他函数组件。
可以嵌套声明普通函数
//函数组件内声明普通函数
function App(){
//声明普通函数
const add = ()=>{
return a+b
}
return null
}
不推荐嵌套声明函数组件
//函数组件内声明普通函数
function App(){
//不推荐嵌套声明函数组件
const Person=()=>{
return null
}
return null
}
嵌套声明组件会 非常慢,并且会导致 bug 产生。
3.内置方法必须在函数组件顶层使用
在React的组件规则中,凡是React内置方法,例如hooks都必须在函数组件的顶层使用。
【函数组件顶层】
1.指的是直接在函数组件的块作用域中,不支持嵌套
"不要错误理解成写在最上面"
示例
import { useEffect, useState } from "react";
export default function Time() {
/*
所有的React方法应该处于"直接作用域"中,
也被称为函数组件顶层
*/
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
setTimeout(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
}, [time]);
return <div>{time}</div>;
}
错误示范
import { useEffect, useState } from "react";
export default function Error() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
/*
useEffect被声明到了if的块作用域中
违反了顶层原则
*/
if(10>5){
useEffect(() => {
setTimeout(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
}, [time]);
}
return <div>{time}</div>;
}
4.组件返回值
React组件和普通函数的区别:
1.React组件必须有返回值
2.React组件的返回值必须是JSX模板(一种可以写js的html结构)
举例
function Header(){
let num = 20
/*
React组件的返回值是一种jsx模板
即html和js混合,{}可以解析特定js代码
*/
return <h1>{num}</h1>
}
组件规范
React组件是个函数
由于React是函数式组件,本质是一个函数,所以一个文件中能声明多个,例如
function App(){
return null
}
const Person=()=>{
return null
}
引用组件时则会采用模块化原则,就和从js文件中引用暴露的函数一样。
//默认暴露
export default function App(){
return null
}
//局部暴露
export const Person=()=>{
return null
}
所以为了避免混乱和项目结构清晰,应该遵循如下规范:
1.React组件所在文件用`jsx`后缀命名,例如`time.jsx`
2.一个`jsx`文件中尽量只声明一个函数组件,并默认暴露该组件。
使用组件
函数式组件虽然是一个函数,但是使用方式与普通函数不同。
区别
//普通函数调用
//使用区域:逻辑代码中
函数名()
//函数式组件
//使用区域:其他组件的html模板中
<函数组件名 />
这是因为普通函数是一段代码逻辑,而函数组件最终返回的是一段包含jsx的html结构。
举例
1.封装Time组件,在time.jsx文件中
//引入react的hook
import { useEffect, useState } from "react";
//暴露函数式组件
export default function Time() {
/*
所有的React方法应该处于"直接作用域"中,
也被称为函数组件顶层
*/
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
setTimeout(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
}, [time]);
return <div>{time}</div>;
}
2.使用Time组件
//从jsx文件中引入组件,引入组件要参考暴露方式
//jsx文件后缀可以省略
import Time from "./time";
function App() {
//react组件在其他组件的html模板中使用
return (
<div>
<Time />
</div>
);
}
export default App;
3.在解析App组件时,注意下面两者的区别:
<div>是小写的,所以 React 知道我们指的是 HTML 标签。<Time />以大写T开头,所以 React 知道我们想要使用名为Time的组件。
组件传参
函数式组件底层是个函数,但是使用时却是仿照html结构,所以传参就是html的所有属性变成对象,再由底层函数接收。
React组件就是一个特殊的函数,他只接收一个参数,就是使用该组件时传入的属性和属性值组成的对象。
父组件传值
import Time from "./time";
import Count from "./count";
function App() {
return (
<div>
<Time />
{/* 父组件传参给子组件,可以传递任意类型 */}
<Count num={1} str="hello" com={<Time />}></Count>
</div>
);
}
export default App;
子组件通过函数形参接收
// 有些写法中会直接解构参数
export default function Count(props) {
/*
按照谁声明谁维护的原则,props是父组件传递的参数
所以对于子组件来说是只读的
如果我们想要在子组件中修改父组件中某些数据,可以让父组件传递修改方法
*/
console.log("父组件传递的参数为", props);
return <div>count</div>;
}
结果查看
{
com: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …},
num: 1,
str: "hello"
}
最终子组件参数得到的就是父组件使用子组件模板时传递的属性组成的对象。
更多父子组件的特性,例如子组件使用、标签体传参、渲染机制将在之后讲解,此处只是介绍一下react组件的概念。