迷人的JSX,你可以进行各种骚操作
您好,我是沧沧凉凉,是一名前端开发者,目前在掘金、知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。
本篇文章是我在去年6月份就准备写的,但那个时候仅仅使用React做了一个多月的项目,所以对很多JSX的使用方法并没有那么得心应手,后面经过长达半年的使用,我发现JSX能够做到的事情太多了,以及JSX创建一些复用性组件上也比Vue模板语法具有更大的优势。
事先声明:Vue和React各有各的优势,本篇文章有些地方是为了对比而不是踩一捧一。
1. 创建可复用组件
能创建可复用组件没有什么稀奇的,现代前端框架都可以创建可复用组件,但在JSX中你创建的组件只要你愿意,组件中的所有内容都可以很方便的进行修改。
在我从Vue切换到React时创建组件的时候去React官方文档上面找过一个Vue有的功能:插槽。
在Vue中只要你创建组件,那么大部分情况都是需要使用到插槽的,但是我在React文档中找了老半天,貌似React中没有这个概念,当时我还感觉到很奇怪,因为没有这个东西那么创建组件的时候复用性就没有那么高。
经过我的仔细研究,我发现React中根本不需要有插槽,因为JSX语法带来了一种更加强大的写法,任何Props
属性都可以作为插槽。
思考一个场景,如果你要更改自定义组件上的图标,或者将文字改成按钮,那你可以考虑下在Vue中怎么进行实现,没错,几乎最好的方式就是使用插槽,那如果你要改变底部的两个按钮的名字呢,使用Vue会如何进行实现?那如果你不想要这两个按钮,想自定义底部,或者根本都不需要底部,那么你使用Vue应该怎么实现呢?
上面的一系列问题是不是让你感到非常的头疼,如果有更多需要自定义的地方,你是不是要在Vue中大量使用插槽,甚至动态插槽?而在JSX中可以很简单的解决这些问题。
我们来看一下下面的这个例子,你可以想象一下如果是使用Vue的话如何进行创建此类型的组件。
在JSX中,没有插槽这个概念,但它具有比插槽更好用的特性,那就是组件上面的所有属性都可以作为插槽,插入ReactNode,比起插槽来讲,JSX的这种方式简便直观,比如下面的这段代码。
import React from "react";
interface TestProps {
children: React.ReactNode;
buttonRender?: React.ReactNode;
}
function Test({ buttonRender, children }: TestProps) {
return (
<>
{children}
{buttonRender}
</>
);
}
export default function App() {
return (
<div className="App">
<Test buttonRender={<button>这里可以传入dom</button>}>测试</Test>
</div>
);
}
最终渲染结果如下:
可不要小看这个功能,在创建自定义组件的时候是非常实用的,它可以给使用这个组件的用户极大的定制权,比如我们改一下上面的代码:
import React from "react";
interface TestProps {
children: React.ReactNode;
buttonRender?: React.ReactNode;
}
function Test({ buttonRender, children }: TestProps) {
return (
<>
{children}
{buttonRender || <button>这里默认的按钮</button>}
</>
);
}
export default function App() {
return (
<div className="App">
<Test>测试</Test>
</div>
);
}
渲染结果:
总的来说,你可以给一个默认的ReactNode,如果用户不传入属性,那么就显示这个默认的ReactNode,如果一旦传入属性值,则替换默认的ReactNode。
但是这里需要强调的是,跟Vue的插槽一样,这种通过属性渲染的ReactNode,是没有办法读取子节点上的属性的,会直接报错:
总之,React的任何Props
属性都可以作为插槽进行使用,这给组件创建带来了极强的可定制性,如果你有使用过Antd,你会发现Antd这个组件库有非常多的地方都可以让你进行定制。
2. 使用变量及函数
在很多时候,我们想要转换一些页面上的值,如果是在Vue模板语法中你要使用第三方库中提供的函数,那么你需要在data中进行声明,才能够在模板语法中使用 。我们以monentjs
中为例,就要下面这么操作:
<template>
<div id="app">
{{ moment().format("h:mm:ss") }}
</div>
</template>
<script>
import moment from "moment";
export default {
name: "App",
data() {
return {
// 这里需要导出才能在界面上使用
moment,
};
},
};
</script>
而在JSX中,你可以直接引用第三方库或者你在其它地方定义的函数,我们还是以monentjs
为例,你可以直接在JSX中进行使用:
import moment from "moment";
export default function App() {
return <div className="App">{moment().format("h:mm:ss")}</div>;
}
在模板语法中使用第三方库会比JSX多出一步导出步奏,而且由于Vue中你声明在data
属性中是会自动进行双向绑定的,可能会导致一些性能上的问题或其它意料之外的BUG,当然你也可以使用Vue高级语法取消双向绑定,甚至冻结该属性,就是操作起来麻烦一点。
3. 任意提取ReactNode
JSX最为灵活的地方就是你可以将任意的ReactNode
写在函数中,直接在JSX中进行使用,这样的好处在于如果你提取成新的组件,那么就涉及到传值的问题,但是你提取成函数,可以直接引用当前函数中已经声明的变量,可能表达起来比较麻烦,所以我们直接看一下下面的例子:
import { useState } from "react";
export default function App() {
const [title, setTitle] = useState("Hello world!");
// 这里可以提取ReactNode
const show = <h1>{title}</h1>;
return (
<div className="App">
{/* 直接使用 */}
{show}
<button
onClick={() => {
setTitle("同样可以改变");
}}
>
点我
</button>
</div>
);
}
上面的代码最终的效果就如下图所示:
JSX具有的这项特性,可以创建非常多的骚操作,同时它可以配合useMemo
进行使用,我在书写项目的时候,有时候也会使用到这种写法,比如下面的情况
import { useMemo, useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const show = useMemo(() => {
if (count === 0) return <div>Hello World!</div>;
return (
<img
src="https://pic.qqtn.com/up/2019-9/15690311636958128.jpg"
alt="图片"
/>
);
}, [count]);
return (
<div className="App">
{show}
<button
onClick={() => {
setCount(1);
}}
>
点我
</button>
</div>
);
}
上面的代码渲染出来就是下面的样子:
4. 递归组件
使用JSX最大的好处之一,递归组件。在Vue模板语法中几乎很难写出递归组件,不过使用Vue也并不是无法写出递归组件。
至于递归组件,我在很早的时候就已经写过一篇对应的文章:React中创建递归组件,所以就不在这里进行赘述了,在项目中导航栏往往会以递归组件的形式来进行编写。
5. 解锁JS完整体
在Vue中,如果你使用了模板语法,会受限于Vue的版本,而无法在模板语法中使用JS的一些新语法,比如在Vue2中无法使用?.
这个非常有用的语法,但是在JSX中,只要你的babel
够新,那么JSX就支持所有的JavaScript语法,比如下面这种语法:
我要大力推荐?.
它的中文名字叫做可选链操作符,ES11
中,它被正式的纳入了规范,我们先来看下面的代码:
const data = {
name: "张三",
age: 18,
sex: "男"
};
console.log(data.friend.name); // 报错:Uncaught TypeError: Cannot read property 'name' of undefined
所以这个时候我们平时就要做很多判断。
const data = {
name: "张三",
age: 18,
sex: "男"
};
console.log(data && data.friend && data.friend.name); // undefined
而有了可选链操作符(?.
)后,我们可以直接简写成:
const data = {
name: "张三",
age: 18,
sex: "男"
};
console.log(data?.friend?.name);
可以发现,代码简化了非常多。
你可能会想,这没什么了不起的,其实这个功能非常的有用,尤其是当数据是从后端获取的情况下,因为后端获取的数据,你完全不知道后端会不会返回undefined
,如果一旦返回的是undefined
,前端在进行调用的时候,Vue可能会出现界面白屏,而React可能会出现界面报错。
我们将上面的代码放在Vue中使用:
<template>
<div>这里是测试:{{ data?.friend?.name }}</div>
</template>
<script>
export default {
name: "App",
data() {
return {
data: {
name: "张三",
age: 18,
sex: "男",
},
};
},
};
</script>
注意!上面的代码在Vue2中会报语法错误:
而在Vue3中可以正常的解析:
这里额外提一句,Vue3最好还是使用新语法,因为Composition API类似于React的Hooks,体验非常好!
<template>
<div>这里是测试:{{ data?.name }}</div>
</template>
<script>
import { reactive } from "vue";
export default {
name: "App",
setup() {
const data = reactive({ name: "张三", age: 18, sex: "男" });
return {
data,
};
},
};
</script>
我们将上面的代码放在React中使用:
const data = { name: "张三", age: 18, sex: "男" };
export default function App() {
return <div>这里是测试:{data?.friend?.name}</div>;
}
6. 完美搭配TS
在Vue中,因为有模板语法的存在,所以Vue对于TypeScript的支持是一个比较大的问题,比如声明在data中的属性,你就无法很好的给他类型,虽然官方为了解决这个问题,推出了vue-class-component
然后又有人在其上进行了一次封装发布了vue-property-decorator
,但依然无法完美解决TypeScript和Vue不搭配的问题。
而JSX可以完美结合TypeScript成为TSX,TSX拥有了更多方便维护的特性,比如泛型组件,可以参考这篇文章:TS与React的完美融合:泛型组件。
而在React中,这些问题都不复存在,React中的JSX可以完美的配合TypeScript,获得TypeScript的所有能力。
还是那个可选链的问题,我们来看看JSX和TSX中的区别。
JSX中有提示但不报错:
TSX中有语法提示并且有错误信息:
7. 最后
当然,React还提供了一些高级用法,虽然在平时的业务代码中可能并不会用到,但是一旦你要进行创建组件,可能会经常用到,因为我个人写组件的情况非常少,但是在Antd的源码中,大量的使用到了这些高级语法来进行创建组件,在之后我可能会深入去了解这些用法。
一旦你用熟JSX后,你就会发现JSX书写起来实在是太美妙了,仿佛再复杂的组件创建起来你也会很有信息,所以无论你是否使用React,JSX都推荐学习,而且现在TypeScript已经内置了JSX解析。
而且就算你使用Vue,你也可以通过对应的bable
使用在Vue中使用JSX语法来创建比较复杂的组件。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!