跳到主要内容

简介

说到React,我们首先想到的就是JSX,JSX是React的核心,它是一种语法糖,其实它会被转换成一个方法:React.createElement(component, props, ...children)

使用jsx可以让我们在JS中写HTML,让我们的代码更加简洁,更加直观。

也正是因为JSX最终都会被编译为React.createElement(component, props, ...children)这种形式的代码,所以React(React 17之前)必须要在自定义组件中进行引入,比如下面的代码:

// 需要引入 React
import React from 'react';

function Test() {
return <div />;
}

而在React 18中,我们就不需要再显示的引入react了,因为React 18中的JSX会被编译为jsx(component, props, ...children)这种形式的代码,而jsx是一个全局的方法,我们可以在任何地方进行调用。

点语法

使用点语法来引用一个React组件其实也是十分常见的,比如Antd中的<Form.Item />组件,还有React文档中提供的下面的例子:

import React from 'react';

const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
},
};

function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}

自定义组件

以小写字母开头的元素代表一个 HTML 内置组件,比如div,span,因为在转换的过程中,小写开头的元素会被转换成字符串传入React.createElement,比如"div","span"。

所以用户自定义的组件必须以大写开头!如 <Foo /> 会编译为 React.createElement(Foo)。而不会将它转换为字符串。

如果你非要以小写开头,那么得将它赋值给一个大写字母开头的变量。

import React from "react";

// 组件应该以大写字母开头,如果非要以小写开头,那么在使用之前需要赋值给一个大写开头的变量
function hello(props) {
return <div>Hello {props.toWhat}</div>;
}

export default function HelloWorld() {
// 赋值给一个大写开头的变量
const Hello = hello;
return <Hello toWhat="World"/>;
}

动态组件

如果要根据某个变量来决定组件的动态渲染,如下面的情况,那么你得先使用一个大写字母开头的变量去接收:

import React, { useState } from 'react';

const components = {
photo: () => <div>显示照片</div>,
video: () => <div>显示视频</div>,
};

export default function Story() {
const [storyType, setStoryType] = useState<'photo' | 'video'>('photo');
const SpecificStory: () => JSX.Element = components[storyType];

return (
<div>
<SpecificStory/>
<button
onClick={() => {
setStoryType('video');
}}
>
切换
</button>
</div>
);
}

效果:

change

条件渲染

React的文档中对于这一部分也介绍的非常详细,具体可以参考条件渲染,这里我只介绍我经常使用到的这一部分。

这里谈及几个常用的,官方的条件渲染这一章讲的更加的详细:

与运算符&&

这个形式的条件渲染我在写项目的时候运用的特别多,这也是我比较推荐的写法,

import React, { useState } from 'react';

export default function TestComponent() {
const [isVisible, setIsVisible] = useState(true);

return (
<div>
{isVisible && <div>isVisible为true的时候才渲染</div>}
<button
onClick={() => {
setIsVisible(!isVisible);
}}
>
切换
</button>
</div>
);
}

最终的渲染结果:

isVisible

三元表达式

import React, { useState } from "react";

export default function TestComponent() {
const [isVisible, setIsVisible] = useState(true);

return (
<div>
{isVisible ? <div>isVisible为true的时候才渲染</div> : <div>isVisible为false的时候才渲染</div>}
<button
onClick={() => {
setIsVisible(!isVisible);
}}
>
切换
</button>
</div>
);
}

具体的渲染如下:

isVisible三元

某些文档或者某些项目上面会以三元表达式来替代与运算符 &&,两者都可以达到同样的效果,但是我个人觉得使用三元表达式来替代&&会增加代码阅读时的复杂性。

import React, { useState } from 'react';

export default function TestComponent() {
const [isVisible, setIsVisible] = useState(true);

return (
<div>
{
// 这里用三元表达式可以实现&&同样的效果
isVisible ? <div>isVisible为true的时候才渲染</div> : null
}
<button
onClick={() => {
setIsVisible(!isVisible);
}}
>
切换
</button>
</div>
);
}

if语句

if语句在做一些比较复杂的交互时特别有用,比如一个界面某个变量为特定值的时候,会渲染不同的元素。

import React, { useState } from 'react';

export default function TestComponent() {
const [count, setCount] = useState(0);

return (
<div>
<IfTestComponent count={count} />
<div>当前count值:{count}</div>
<button
onClick={() => {
setCount(count + 1);
}}
>
切换
</button>
</div>
);
}

function IfTestComponent({ count }: { count: number }) {
if (count === 1) {
return <div>这是count等于1时的渲染</div>;
}

if (count === 2) {
return <div>这是count等于2时的渲染</div>;
}

if (count === 3) {
return <div>这是count等于3时的渲染</div>;
}

// 其它情况下就不再进行渲染
return null;
}

最终的渲染结果如下:

countChange

循环

关于循环的内容有点多,可以参考一下官方的列表 & Key这篇文章,我在之前做项目中,手动进行列表遍历渲染的情况并不多,一般这种数据都是直接放入Antd的Table组件中,以表格的形式展示出来。

我这里主要讲解一下循环的用法:

import React from "react";

const data = [
{ id: 1, name: '张三', age: 18, sex: '男' },
{ id: 2, name: '李四', age: 19, sex: '男' },
{ id: 3, name: '王五', age: 17, sex: '女' },
];

export default function TestComponent() {
return (
<div>
{data.map((item) => (
<div>
姓名:{item.name},年龄:{item.age},性别:{item.sex}
</div>
))}
</div>
);
}

其实你打开浏览器的控制台,你会发现一个警告:

image-20220322143638978

根据我的经验来说,只要你使用了map,那么你需要在map返回的元素中绑定key值:

<div>
{data.map((item) => (
// 绑定key值
<div key={item.id}>
姓名:{item.name},年龄:{item.age},性别:{item.sex}
</div>
))}
</div>

这个时候警告就没了,可能你有时候不想在外面嵌套一层div,那么你可以使用空标签<Fragment />

export default function TestComponent() {
return (
<div>
{data.map((item) => (
// 绑定key值
<React.Fragment key={item.id}>
姓名:{item.name},年龄:{item.age},性别:{item.sex}
</React.Fragment>
))}
</div>
);
}

Props

在JSX中,Props的处理是非常灵活的,也正是因为灵活的Props,让React在创建一些复杂组件的时候就会显得异常的方便。

字符串字面量

当如果你要传入的prop是一个字符串时,你就可以省略括号,如下所示:

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

React官方说这种行为是不重要的,随便用哪一种都可以,但是我个人习惯在prop为字符串的时候省略掉括号。

Props的默认值

当如果不给prop赋值,那么它的默认值为true,也就是说下面的两种写法是等价的。

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

官方表示不建议不传递 value 给 prop...然而我非常喜欢当为true时不传值给prop。

属性展开

我特别喜欢React的可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象,更妙的是,使用ES6的语法可以只将部分属性传递下去。

import React from 'react';

export interface ButtonProps {
kind: string;
onClick?: () => void;
children?: React.ReactNode;
}

const Button = (props: ButtonProps) => {
// 这里可以使用对象的解构,将kind属性保留下来,剩余的其它属性传入button中
const { kind, ...other } = props;
const className = kind === 'primary' ? 'PrimaryButton' : 'SecondaryButton';
return <button className={className} {...other} />;
};

export default function TestComponent() {
return (
<div>
<Button kind="primary" onClick={() => console.log('clicked!')}>
Hello World!
</Button>
</div>
);
}

该特性在对一些UI组件进行二次封装上特别的好用,并且配合TypeScript,可以获得非常完美的组件使用提示。

children

布尔类型、Null 以及 Undefined 将会忽略,但是0依然会被渲染

import React from 'react';

export default function TestComponent() {
return (
<div>
{0}
{undefined}
{null}
{false}
{true}
</div>
);
}

最终渲染结果:

image-20220322145017918

所以警惕下面这种代码,可能会引起不必要的BUG

import React from "react";

const list = [];

export default function TestComponent() {
return (
<div>
{list.length && <div>这里不会渲染</div>}
</div>
);
}

渲染结果和上面的图一样,为0

子元素

相信React子元素的用法我们在平时写项目中已经非常熟悉了,但是还有一种形式我们用的比较少,比如下面这种返回一个数组的形式:

数组

需要注意的是,如果返回的是存储在数组中的一组元素,那么还的给这些元素设置key值,不然会抛出异常。

import React from 'react';

export default function TestComponent() {
// 不需要用额外的元素包裹列表元素!
return [
// 不要忘记设置 key :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}

渲染结果:

image-20220322150127488

函数

下面这种用法其实我在项目中没有使用过,但是官方上有提到,所以这里直接放出官方的代码:

import React from 'react';

// 调用子元素回调 numTimes 次,来重复生成组件
function Repeat(props: {
numTimes: number;
children: (index: number) => React.ReactNode;
}) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}

export default function TestComponent() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}

渲染结果:

image-20220322150347777

参考资料: