前置说明
本系列文章主要讲在有Vue框架的基础下如何更快、更高效的入门React,主要通过类比Vue来学习React的基础知识。
- 基本不讲源码、偶尔深入原理
- 不讲类组件。all in React Hooks
本篇要点:如何将UI在React中描述出来
Vue快速转React指南(一):
- CLI(脚手架)的差异
- JSX vs. template模板
- JSX语法规范
开始
CLI
Vue
首先是新建一个环境,Vue的脚手架是:
npm init vue@latest
最新的Vue要求node版本大于16。
选择的时候在JSX Support选择yes,并且加上路由Vue Router和状态管理Pinia,其他的随意。
我选择的是不使用TS以及不装ESLint和测试相关。生成的代码如我所愿。
React
React官方的脚手架叫作create-react-app
,先npm install -g create-react-app
全局安装之后,执行:
npx create-react-app my-app
就会生成一个有基础框架功能的React项目框架,并没有像 Vue CLI有一个选择的过程。 如果想要一个包含其他功能的模板代码,需要这样:
npx create-react-app my-app --template [template-name]
具体有哪些template可选择需要在npm上查看cra-template-*:
我截图了前三个模板。举个例子,比如cra-template-redux
这个模板的简介是:The official Redux+JS template for Create React App,表示这是一个用来搭建一个包含Redux状态管理功能的官方模板。
注意,在使用这个模板的时候,不需要输入前缀cra-template-
,只需要输入最后一个词,那么我们按照这个模板创建一个包含redux的代码:
npx create-react-app my-app --template redux
等到装好之后,查看文件目录你会发现除了添加了redux之外,比起Vue的模板还多了一些不太能看懂的文件,比如reportWebVitals.js
,以及一些测试文件,例如App.test.js/counter.spec.js
。
这些是什么作用我就不细讲了,总结来说就是默认添加了一些对于学习来说不需要的功能代码,并且这些功能看起来写在模板里但是实际都没有生效,因为都是默认关闭的,不影响运行和学习。
以上就是脚手架的区别。
JSX vs. template
这篇不讲React Hooks,我们先说JSX。
Vue and template模板
Vue中的main.js对应react里的index.js,先看看里面都有些什么:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
const app = createApp(App)
app.use(createPinia()) // 注册状态管理
app.use(router) // 注册路由
app.mount('#app')
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './index.css';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(); // 这个不用管
多多少少能类比一些对应关系,比如Vue里的app.mount('#app')
就和react里const container = document.getElementById('root'); const root = createRoot(container);
差不多,看起来是将实例挂载到id
为app
的DOM上。
在说root.render(...)
这坨代码之前,我们先讲Vue的template模板语法。
一个前端框架的诞生需要几步这篇文章最后分析Vue里的编译时讲到,template最终是被编译到了script
里的render
函数里:
// 编译前
<template>
<div @click="handler"> click me </div>
</template>
<script>
export default {
method: { handler: () => {...} }
}
</script>
// 编译后
<script>
export default {
method: { handler: () => {...} },
render() { // template的编译结果
return { tag: 'div', props: { onClick: handler }, children: 'click me' }
}
}
</script>
相当于你的UI声明代码不写在template里,直接写编译后的结果在render函数里,也是一样可以运行的,为了最终的渲染,这是必经之路。这样子.vue
结尾的文件只剩下script代码
,那么最终还是会变成一个JS文件。
既然template这种Vue自创的语法可以通过Vue的编译器编译到render函数
里,如果我自创一个语法和编译器,这个编译器能够产生的编译结果提供给render函数和Vue编译template的结果是一样的,是不是说明我就可以在Vue里使用我自己自创的语法了。
所以,有另外一套语法叫做JSX,全称JavaScript XML。
- JSX与HTML、template的相同点
JSX写法和HTML有点像,就像template和HTML有点像一样。babel有个包用来专门将JSX语法编译为JavaScript代码,就像Vue里专门有个编译器将template的内容全部编译到script
里的render
函数里一样。
- JSX与template的不同点
不同点在于template语法是Vue自创的,有一套属于它自己的语法体系和编译器,只能在.vue
里运行。JSX是XML语言的一种,也被叫作JavaScript的拓展语法,也有一套语法体系,编译器是babel实现,百度百科说react是最早使用JSX的框架(俺也不知道是不是。
虽然Vue主推template写法,但是它也支持JSX写法,比如我们改造一下HelloWorld.vue
这个组件为HelloWorld.jsx
:
// HelloWorld.vue
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
上面组件里把setup
放到script上魔改了一下,这种魔改需要依赖Vue的处理。我们去掉setup
之后就可以写一个正常的JS代码了:
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: ['msg'],
setup(props) {
// setup
}
})
</script>
然后,新建一个HelloWorld.jsx
文件,vue3只需要在setup里返回template里的片段,按照JSX的语法来写,然后把组件里的css代码单独抽成一个HelloWorld.css
文件引入:
// vue3
import { defineComponent } from "vue";
import './HelloWorld.css'
export default defineComponent({
props: ['msg'],
setup(props) {
return () => (
<div class="greetings">
<h1 class="green">{props.msg}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
)
}
})
// vue2
import './HelloWorld.css'
export default {
props: ['msg'],
render() {
return (
<div class="greetings">
<h1 class="green">{this.msg}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
)
}
}
然后在App.vue
里将HelloWorld.vue
组件改为HelloWorld.jsx
,可以看到代码一样是运行成功的。
react and JSX
上面对Vue组件进行JSX改造之后,代码的结构就和react很像了。
我们可以发现,template其实是Vue为了降低学习MVVM框架的入门难度而模拟了一个类似在HTML文件里写HTML代码的东西,最终还是会编译成JS文件。但是react不帮我们做这一步,直接在JS文件里写JSX语法。
在后续的react学习中,还有很多Vue框架内部帮我们实现了但是react并没有实现的东西,这些框架内部未实现的东西都需要框架使用者在使用框架的时候自行去实现,至于怎么实现、怎么优化都是需要使用者深入学习的。
这也是为什么初学者会觉得react很难上手的原因。
比如App.js
文件,我们看到:
import React from 'react';
import logo from './logo.svg';
import { Counter } from './features/counter/Counter.js';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Counter />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
</div>
);
}
export default App;
上面代码中的Counter
和App
在react里就是一个组件单位,类似于Vue里.vue
结尾的单文件组件(SFC),我们可以把它想象成template编译后的JS文件。
这也是react的精髓所在,项目里全是JS文件,就可以任意使用JS语法,不会有.vue
文件里template语法的约束。因为作为一个前端开发人员,.js
是你常写的东西,如果你没有接触过.vue
的话还需要去学习对应的语法。
在JS文件里写UI相关的代码就会比template灵活多了,比如下面是大家常举的一个例子:
根据传入的level
参数来决定渲染哪一个h标签
Vue的template写法是这样:
//.vue文件
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
在react里用jsx这样实现:
// 在react里不是原生的HTML标签需要大写
function App({level}) {
const H = 'h' + level;
return <H></H>
}
我对这两种写法的看法是万事没有绝对,灵活不一定代表好,有约束不一定不好。视情况而定。
JSX语法
简单讲一下JSX语法,有一些部分和HTML、template很像。
- 使用
{}
包裹变量
和Vue里的{{}}
一个用途。
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
- DOM事件和HTML一致,只是需要大写
const clickHandler = () => {
alert('click')
}
<div onClick={clickHandler}></div>
- 属性和HTML一致
在react里,因为类组件使用了ES6的class
语法,所以react为了避免判断错误将class
属性改为className
;label
标签的for
属性,因为和for循环的for同名,也将label的for
属性改为htmlFor
。
Vue里的JSX写法不受影响,完全一致。
// react
<img src={logo} className="App-logo" alt="logo" />
<label htmlFor='111'></label>
- react里组件名必须大写
为了和原生HTML标签做区分,react是组件名称必须要大写
import { Counter } from './features/counter/Counter';
function App() {
return (
<Counter />
)
}
- JSX片段只能有一个根元素
一般我们会用一个div
标签去包裹住所有的元素,但是如果想要实际渲染出来的组件DOM里没有别的元素的话可以用一个空标签去包裹。
function App() {
return (
<>
<h1></h1>
<Counter />
</>
)
}
ps: 其实return
的时候可以不用()
包裹住JSX片段,但是为了看起来规范些,一般会这样写。
- JSX里的标签必须要有结尾符号
<img>
在HTML里这样写是可以的,但是在JSX里必须要这样写<img />
或者<img></img>
OK,看到这里你应该会写JSX了。
最后
回顾本篇文章要点:
- CLI(脚手架)的差异
- JSX vs. template模板
- JSX语法规范
看完上面应该就会使用JSX去描述我们所需要的UI,就像静态的HTML页面一样。接下来我们要学习动态的交互事件,看看这个在react里是怎么实现的,让UI动起来。
预告: 下一篇主要讲组件里的参数state vs. Vue里的响应式数据data