只会Vue的我,上班第一天就要我写React+TS,是种什么样的体验?
前言
只会Vue的我,上班第一天就要我写React+TS,是种什么样的体验?
入职第一天,坐公交来到指定报道处,拿号排队准备办理入职,拿装备去工位,认识 Leader 跟 Mentor,自己看文档装环境,屁股都没坐热,mentor 就问我技术栈是什么,我说 Vue,他又问 React 用过吗?我开始有不祥的预感,他反手就甩了个链接,叫我装完环境看一下,装完vscode、node、git,我直接 pull 下来,看了一下目录 pnpm 咱也没用过啊,怎么全是 ts ,用的 Vite?这个我熟,直接安装依赖 run dev!
首先,这个项目就两个页面,我第一感觉是:怎么看着还不如我自己的项目好看😢,但是当我打开代码,脑子直接缺氧,啥都看不懂。什么 type 什么 useMemo,闻所未闻。
赶紧开始自学 React + TS ……
晚上回到出租屋,突然有一种压力油然而生,我是不是太蠢了,Mentor 会不会看不起我 ……
看了一天视频,文章,我觉得我已经了解了 React ,至少不会看不懂了。
这篇文章就来总结一下我是如何快速上手的,分享一下我心得体会。如果你也跟我一样,下面我分享一些我的经验,希望能够对您有所帮助。
项目创建
在 Vue 中,我们常用的脚手架是 Vue-CLI 以及尤大大推荐的 Vite ;在 React 中,我们通常使用 creat-react-app(CRA)创建项目,当然 Vite 同样也支持我们创建 React 项目。
使用 CRA
npx create-react-app my-react-app
使用 Vite
yarn create vite my-react-app --template react
其他创建方式
注意
值得注意的是使用 cra 创建的项目中,JS 文件就是正常的.js
后缀,而 Vite 创建项目的文件后缀是.jsx
。
- Vite 基于 ESModule,它提供了对 JSX 的原生支持,使用
.jsx
作为源文件的后缀,以便我们直接编写 JSX 语法的 React 组件。 - CRA 默认没有启用 JSX 预处理器,而是通过 Babel 和 Webpack 等工具来处理 JSX 语法。
在写法上没有区别,不用考虑太多。
项目目录
以下是 Vite 创建的项目目录结构:
其中入口文件是main.jsx
,如果是 CRA 创建的项目则是index.js
。
让我来看看它的代码:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
这跟 Vue 的main.js
的作用一样,将App
根组件挂载到id
为root
的 DOM 元素上。
我猜你可能会想,这里引入一个 React 又引入一个 ReactDom 干嘛,它们两个有什么区别?
我们在package.json
中也可以看到这两个依赖。
这是因为 React 作者将 React 源码拆成了两部分:
-
React
库提供构建用户界面的各种 API 和组件,比如创建 React 组件、处理组件生命周期、状态管理、事件处理等核心功能。 -
ReactDom
库是 React 的 DOM 渲染器,只负责将 React 组件渲染到浏览器的 DOM 中,提供一些管理组件的更新、挂载和卸载等操作的 API。
JSX
我们return
出来的 JSX 模板,如果要换行的话,需要用()
包裹一下,然后这个 JSX 表达式必须具有一个父元素,什么意思呢?就是下面这种写法会报错,JSX表达式规定了最外层只能有一个父元素,这种情况下我们一般会在最外面套一个div
或者空标签<></>
。
return (
{/*报错*/}
<div>
<h1>hello</h1>
</div>
<div>
<h1>你好</h1>
</div>
)
return (
<>
<div>
<h1>hello</h1>
</div>
<div>
<h1>你好</h1>
</div>
</>
)
在JSX表达式允许我们只能在{}
中写JS代码
创建组件
在 React 中有两种创建组件的方式,函数组件和类组件,类组件是。现在界内大部分都使用函数组件,函数组件相比于类组件,性能更好,React 18 虽然还支持类组件,但是更推荐使用函数组件。
函数组件
语法
函数组件是通过函数来定义的,函数的返回值是 JSX 模板,可以使用 function
关键字或箭头函数定义。
const App = () => {
return (
<div>
<h1>Hello Function Component</h1>
</div>
);
};
export default App;
状态管理
函数组件通过 React 提供的 Hooks函数(官方提供的一般以use
开头的 API),比如我们通常使用useState
钩子来管理状态。
useState
返回一个由两个值组成的数组:
- 当前的
state
,初始值为传递给useState
的参数。 set
函数,它可以让你将state
更新为不同的值并触发重新渲染。
🙋举个例子🌰,这里我们实现一个计数器,点击count
自增1
:
import { useState } from 'react';
const App = () => {
let [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
}
return (
<div>
<h1 onClick={handleClick}>{count}</h1>
</div>
);
};
export default App;
这里我们解构得到count
跟它的set
方法,我们每次点击触发handleClick
函数,函数内部通过set
方法修改状态并重新渲染。

生命周期
React 16.8之前,函数组件没有生命周期方法,但是从React 16.8开始,可以使用useEffect
这个 Hook函数来执行类似生命周期的逻辑,用于替代 componentDidMount
、componentDidUpdate
和 componentWillUnmount
这些方法。
useEffect
useEffect
是 React 中用于执行副作用操作的 Hook,并且具有类似于生命周期方法的功能。
useEffect
接受两个参数:副作用函数和依赖数组。
- 副作用函数:第一个参数是一个函数,用于执行副作用操作。
- 依赖数组:第二个参数是一个数组,包含了副作用函数中所依赖的变量。如果省略这个参数,那么副作用函数会在每次组件重新渲染时都执行,可以充当
componentDidUpdate
这个生命周期;如果传入空数组[]
,则副作用函数只会在组件挂载时执行,相当于componentDidMount
;如果依赖数组中包含了某些变量,则只有这些变量发生变化时,副作用函数才会重新执行。如果我们在其中return
一个函数,这个函数将会在组件卸载时除非,相当于componentWillUnmount
。
我总结了一下副作用函数执行时机与依赖项的关系如下:
依赖项 | 副作用函数执行时机 |
---|---|
没有依赖项 | 组件初始渲染 + 组件更新时执行 |
空数组依赖项 | 只在初次渲染时执行一次 |
添加特定依赖项 | 组件初始渲染 + 特定依赖项变化时执行 |
举例说明🌰:
//没有依赖项数组
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('组件更新');
});
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
export default App;
这里我们没有依赖项数组,刷新页面,组件初次渲染打印(打印两次是因为React源码自己执行了一次,应该是跟严格模式有关,这里我们不用深究),每次点击都会打印,可以充当componentDidUpdate
生命周期。

依赖项为空数组呢?
//依赖项为空数组呢?
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('组件挂载');
},[]);
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
export default App;
这里我们的依赖项数组为空,刷新页面,只在初次渲染时打印,点击不打印,可以充当componentDidMount
生命周期。

依赖项数组不为空呢?
很容易联想到,如果数组里面有会发生改变的项,则会触发副作用函数,否则不会触发,这里就不演示了。
来看最后一个,在副作用函数中return
一个函数:
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('组件更新');
return () => {
console.log('组件卸载');
}
},[count]);
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
export default App;
这里我们return
一个函数打印卸载,表示组件卸载时会执行的操作。我们通常在这里进行组件卸载时的执行的操作,可以充当componentWillUnmount
生命周期。

一个很常见的例子🌰:
我们通常在组件卸载时清除一些副作用函数,如定时器
import React, { useState, useEffect } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setCount(count + 1);
},1000)
return () => {
clearInterval(timer);
}
},[count]);
return (
<div>
<p>点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
export default App;
如果我们不及时清除定时器,会导致资源浪费。
而且由于 setCount
是一个异步操作,它不会立即更新 count
的值,而是在下一次渲染时才会生效。因此,即使你设置了定时器每隔一秒钟执行一次,但 count
的值并不会每秒增加一次。相反,当定时器触发时,count
的值仍然是上一次渲染时的值。等一段时间后就会看到下面这个情况:

类组件
😁可以不用但不能不学!
语法
类组件通过继承 React.Component
或其子类来创建,类组件中有一个 render()
方法,用来返回 JSX 语法。
拿代码说事:
import { Component } from "react";
class App extends Component{
constructor(){
super();
}
render(){
return(
<div>
<h1>Hello Class Component</h1>
</div>
)
}
}
export default App;
类组件的语法就是声明一个类,这个类继承React.Component
或者子类,render()
方法返回 JSX 模板。然后将我们的 App 组件挂载到页面上就可以了。
状态管理
类组件拥有内部状态state
,通过 this.state
来访问组件的状态,使用 setState()
方法来更新状态,触发组件的重新渲染。
import { Component } from "react";
class App extends Component{
constructor(){
super();
this.state={
name:"张三"
}
}
handleClick(){
}
render(){
return(
<div>
<h1 onClick={()=>this.setState({name:"李四"})>{this.state.name}</h1>
</div>
)
}
}
export default App;
我们绑定onClick
事件,当点击时修改name
的状态。

生命周期
类组件中可以使用生命周期方法,常见的有componentDidMount
、componentDidUpdate
、=跟componentWillUnmount
,它们分别对应 Vue3 中的 onMounted
、onUpdated
跟 onBeforeUnmount
。
这个网站可以很清楚看到 React 的生命周期,React生命周期,这里就不过多赘述。
组件通讯
在 Vue 中组件传值一般通过props & emit
、provide & inject
以及Vue2中的EventBus
,或者我们可以直接使用状态管理工具Vuex
或Pinia
,React 其实大差不差,同样也有自己的状态管理工具。
父传子
父组件通过props
传递任意的数字、字符串、布尔值、数组、对象、函数甚至是JSX,子组件通过props
参数接收。
🙋♀️🌰:
const Son = (props) => {
return (
<>
<p>{props.name}</p>
<p>{props.age}</p>
<p>{props.cb()}</p>
<p>{props.child}</p>
</>
);
}
const Father = () => {
return (
<Son
name={'阳阳羊'}
age={20}
cb={function cb(){
console.log(123);
}}
child={<span>i am jsx</span>
}>
</Son>
);
};

⚠️需要注意的是:子组件只能读取props
中的数据,不能直接进行修改,父组件的数据只能由父组件修改,或者通过父组件传过来的函数进行修改。如果在子标签中嵌套元素,可以在子组件的props.children
中获取。
子传父
子传父通过最原始的回调函数方式,父组件给子组件传一个函数,子组件调用这个函数然后传入实参,在函数内部用set
方法修改父组件的状态。
🙋♀️🌰:
const Son = (props) => {
return (
<button onClick={()=>props.cb('小羊')}>点击传递</button>
);
}
const Father = () => {
let [name, setName] = useState('阳阳羊');
let cb =(name)=>{
setName(name);
}
return (
<>
<Son cb={cb}></Son>
{name}
</>
);
};

⚠️这里需要注意:onClick
事件处理程序是一个函数,写成onClick={() => props.cb('小羊')}
时,实际上创建了一个匿名函数,当点击按钮时会调用这个函数,然后调用props.cb('小羊')
,如果直接写成onClick={props.cb('小羊')}
,在渲染时就会立即调用props.cb('小羊')
,而不是在点击时调用。
兄弟组件通信
兄弟组件通信贯用手段就是状态提升,说白话就是将数据都交给父组件,然后父组件作为中间商派发给大家。
🙋♀️🌰:
const Son1 = (props) => {
return (
<>
子组件1-
<button onClick={()=>props.cb('小羊')}>传给子组件2</button>
</>
);
}
const Son2 = (props) => {
return (
<>
<p>子组件2-{props.name}</p>
</>
);
}
const Father = () => {
let [name, setName] = useState('Son2');
const cb = (newName)=>{
setName(newName);
}
return (
<>
<Son1 cb={cb}></Son1>
<Son2 name={name}></Son2>
</>
);
};
这里子组件1
通过触发父组件传递过来的函数,并传递参数,然后父组件接收到参数修改数据源,传递给组件2
。这样就完成了兄弟组件通信。

夸层组件通讯
思路是将两个组件放在同一个上下文中就能实现数据共享。步骤如下:
- 使用
createContext
方法创建一个上下文对象,将组件Provider
,Consumer
抛出
import React from 'react';
const { Provider, Consumer } = React.createContext()
export {
Provider,
Consumer
}
-
在顶层组件(App)中通过
Provider
组件提供数据 -
在底层组件(B)中通过
Consumer
组件获取对应数据
import { Provider, Consumer} from "./provider";
const ComponentB = () => {
return (
<Consumer>
{
value => (
<div>
<h1>底层组件——{value}</h1>
</div>
)
}
</Consumer>
);
}
const App = () => {
const value = "顶层组件的数据"
return (
<Provider value={value}>
<h1>顶层组件</h1>
<ComponentB/>
</Provider>
);
};
效果如下:

状态管理工具
常见的 React 状态管理工具有 Redux、Mobx、Rcoil 等等,如果有感兴趣的同学,我可以单独出一篇文章详细聊聊。本篇就不展开聊了。
自定义Hooks
除了 React 官方提供的一些 Hooks 之外,还允许我们自定义自己的 Hooks,我们需要遵循以下规则:
- 声明以
use
开头的函数 - 在函数体内封装可复用的逻辑
- 把组件中使用到的状态
return
出去 - 在哪里用到就在里面执行解构
🙋♀️🌰:
我们设计来设计一个开关 Hooks,作用是对值取反,从而控制元素的显示隐藏:
import { useState } from "react";
function useToggle (){
const [value,setValue] = useState(true);
const toggle = () => {
setValue(!value);
}
return [value,toggle];
}
const App = () => {
const [value,toggle] = useToggle();
return (
<div>
<h1>{value?"开":"关"}</h1>
<button onClick={toggle}>按钮</button>
</div>
)
}
export default App;
这里我们借助useState
进行封装,将需要用到的变量进行抛出,在需要使用的地方解构拿到,通过三目运算符进行显示隐藏。

Hooks使用规则
Hooks 并不是然后场景下都能用,像比如在类组件中是不能直接使用 Hooks 的,除非使用高阶函数进行包装,以及其他许多规则:
- 不能在组件外部使用
- 不能在判断语句中使用
详见 React中文官网
总结
还是那句话,不管是 Vue 跟 React 都有自己的优缺点,我们不能断言说哪个好哪个差,在我看来 Vue 更适合第一次接触前端框架的小白,它封装了许多 API,对新手很友好,上手即用;而 React 相对复杂,更考验开发者的思维,对于一些复杂需求能够自定义进行封装,更接近原生 JS 开发。大家按需求选择就好。
参考
最后
码字不易,感谢三连!后续有关 React 的学习会持续更新!
已将学习代码上传至 github,欢迎大家学习指正!
技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!
转载自:https://juejin.cn/post/7377320107929829388