从Vue到React的过渡之路,快速上手React。
“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情”
本篇也是自己记录从一个Vue开发者学习过渡到React的一些学习笔记。会简单对比下两大框架的写法,更适合Vue开发者过渡到React阅读食用,理解错误或者有偏差的地方也请大家指出。
React
1. 什么是React?
- React 是 Facebook 创建的一个开源项目,最受欢迎的库之一,在GitHub中超过了150000 Star。
- 一个专注于构建用户界面的 JavaScript 库,和vue和angular并称前端三大框架,不夸张的说,react引领了很多新思想,世界范围内是最流行的js前端框架,最近发布了18版本,加入了很多新特性。
- React 是 MVC 应用程序的视图层(Model View Controller)
其特点就是:
- 声明式
React使用的(JSX语法),而 Vue 中将 HTML,CSS,JS 都分离了。所以很多初级前端开发者可以很好的学习 Vue,而学习 React 需要对 JavaScript 相对有一定要求。这也是更多新手前端开发者和初级前端开发者更热爱 Vue 的一个原因。
- 组件化
组件是react中最重要的内容,构建管理自身状态的封装组件,然后对其组合以构成复杂的 UI。
- 跨平台
react 既可以开发web应用也可以使用同样的语法开发原生应用(react-native),比如安卓和ios应用,react更像是一个
元框架为各种领域赋能。
2. 开始
我们先打开命令行窗口安装一个React的项目,当然你也可以通过 Script 标签在线引入。
$ npx create-react-app react-demo // 创建项目
注意:
1. 这里的 npx create-react-app 是固定命令,create-react-app 是 React脚手架的名称。
2. react-demo 是项目名称,可以自定义,但是要注意一下语义化。
$ cd react-demo && npm start // 启动
启动完就得到了一个 localhost:3000 新窗口
接下来就让我们开始我们的 React 之旅,我们简单从JSX语法开始吧!
3. JSX语法
1. 表达式
const name = '小明'
// 用JSX方式创建React元素
let H1 = <h1>Hello React,我是:{name} </h1>
// 渲染React元素到页面中
ReactDOM.render(H1,document.getElementById('app'))
<template>
<div>
<h1>Hello React,我是:{{name}} </h1>
</div>
</template>
2. 条件渲染
const isShow = true
const state = () => {
return isShow ? (<div> 成功! </div>) : (<div> 失败! </div>)
}
const result = (
{ state() }
)
<template>
<div>
<div v-if="isShow"> 成功! </div>
<div v-else> 失败! </div>
</div>
</template>
3. 列表渲染
const musicList = [
{name: '我是如此的相信' ,id: 1},
{name: '烟灰易冷' ,id: 2},
{name: '你算什么男人' ,id: 3},
{name: '本草纲目' ,id: 4}
]
const playMusic = (
// 这里的 Key 值也是必须要写的 有 id 可以用 id 没有的话可以用 index 下标
// Key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用
<ul>
{ musicList.map(item => <li key={item.id} >{ item.name }</li>) }
</ul>
)
<template>
<div>
<ul>
<li v-for="item in musicList" :key="item.id">{{item.name}}</li>
</ul>
</div>
</template>
4. 样式处理
// 行内样式
function App() {
return (
<div style={{color: 'red'}}> 雨纷纷,旧故里草木深。 </div>
)
}
or
const styleData = {
color: red,
fontSize: 18px
}
function App() {
return (
<div style={ styleData }> 雨纷纷,旧故里草木深。</div>
)
}
// 类样式
const showStyle = true
// css
.title {
color: red,
font-size: 18px
}
function App() {
return (
// 在这里你会注意到我们 class 的写法是 className 而不是 calss
// 我们需要知道我们编写的其实就是 JavaScript ,而不是真正的 HTML
<div className={ showStyle ? title : '' } > 我听闻 你始终一个人。 </div>
)
}
export default App
4. 组件
1. 类组件
提起类 我们先来回顾一下
es6中类的基本语法吧,我平常是基本没用到的 ^_^。
// js
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
// 上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
// ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过`class`关键字,可以定义类。
// 下面的代码用 ES6 的 class 改写的
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
这是ES6入门的解释 对类有兴趣的可以去详细看一看
其实没必要过多余纠结 类组件,下面主要会围绕函数组件来说。经过多年的实战,函数组件是一个更加匹配React的设计理念 UI = f(data),也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks的诞生解决了我们的一系列问题,后续我们会讲到Hooks。
// 引入React
import React from 'react'
// 定义类组件
class MusicCom extends React.Component {
render () {
return <div>缘份落地生根是我们!</div>
}
}
function App () {
return (
<div className="App">
// 渲染类组件
<MusicCom />
<MusicCom></MusicCom>
</div>
)
}
export default App
// MusicCom.vue 组件
<template>
<div>缘份落地生根是我们!</div>
</template>
// App.vue
<template>
<div class="App">
<MusicCom />
<MusicCom></MusicCom>
</div>
</template>
2. 函数组件
// 定义组件最简单的方式就是编写 JavaScript 函数
function MusicCom () {
return <div>我听闻 你始终一个人!</div>
}
function App () {
return (
<div className="App">
// 渲染函数组件
<MusicCom />
<MusicCom></MusicCom>
</div>
)
}
export default App
// MusicCom.vue 组件
<template>
<div>我听闻 你始终一个人!</div>
</template>
// App.vue
<template>
<div class="App">
<MusicCom />
<MusicCom></MusicCom>
</div>
</template>
3. 函数组件的事件
- on + 事件名称 = { 事件处理函数 } ,比如:
<div onClick={ onChange }></div> - react事件采用驼峰命名法,比如:onMouseEnter、onFocus
// 基础事件
function HelloCom () {
const onChange = () => {
console.log('我被点击了')
}
return (
<button onClick={onChange}>点击</button>
)
}
// 事件对象
function HelloCom () {
// 滚轮事件的相关参数
const handleScroll = (e) => {
console.log(e)
}
return (
<button onWheel={handleScroll}>点击</button>
)
}
// 额外参数
function HelloCom () {
const musicList = [
{name: '我是如此的相信' ,id: 1},
{name: '烟灰易冷' ,id: 2},
{name: '你算什么男人' ,id: 3},
{name: '本草纲目' ,id: 4}
]
const onChange = (val) => {
console.log(val) // 点击第一个 => "我是如此的相信"
}
// 注意: 一定不要在模板中直接写函数调用 onClick={() => onChange(item.name) } 错误!!!
return (
<ul>
{ musicList.map(item => <li key={item.id} onClick={() => onChange(item.name) } >{ item.name }</li>) }
</ul>
)
}
4. 组件状态
在Vue中我们对于我们的数据是有响应式的,所以我们在更改数据的时候,可以直接更改。
而在React中我们需要特定的语法去修改数据,让数据驱动视图,页面自动刷新。在React hook出来之前,函数式组件是没有自己的状态的,所以我们通过类组件来讲解说明。

- 初始化状态
通过class的实例属性state来初始化值,这是对象结构,可以拥有多个数据状态。
class Counter extends React.Component {
state = {
count: 0
}
render() {
return <button>计数器</button>
}
}
- 读取状态
通过this.state读取
class Counter extends React.Component {
state = {
count: 0
}
render() {
return <button>计数器{this.state.count}</button>
}
}
- 修改状态
this.setState修改状态,更新视图。
class Counter extends React.Component {
state = {
count: 0
}
// 定义修改数据的方法
setCount = () => {
this.setState({
count: this.state.count + 1
})
}
// 读取数据 并且绑定事件调用方法修改
render () {
return <button onClick={this.setCount}>{this.state.count}</button>
}
}
注意: 这里不能直接
state中的值,必须通过setState方法进行修改
5. 组件通讯
组件通讯想必这个大家都很清楚了,日常中我们的组件通讯大致上分为三种:
1. 父传子 2. 子传父 3. 兄弟组件传值。
在Vue中使用的 props $emit 父传子 子传父 大致上和React是差不多的。
1. 父传子
// 子组件通过 props 接收
function Son(props) {
console.log(props)
return (
<div>
{props.msg}
</div>
)
}
// 父组件
class App extends React.Component {
state = {
message: '听妈妈的话 别让她受伤'
}
render() {
return (
<div>
<div>父组件</div>
<Son msg={this.state.message} />
</div>
)
}
}
2. 子传父
// 子组件
function Son(props) {
function handleClick() {
// 调用父组件传递过来的回调函数 并传入参数
props.changeMsg('想快快长大 才能保护她')
}
return (
<div>
{props.msg}
<button onClick={handleClick}>点击</button>
</div>
)
}
class App extends React.Component {
state = {
message: '听妈妈的话 别让她受伤'
}
// 提供回调函数
changeMessage = (newMsg) => {
// 子组件传过来的数据 newMsg
this.setState({
message: newMsg
})
}
render() {
return (
<div>
<div>父组件</div>
<Son
msg={this.state.message}
changeMsg={this.changeMessage}
/>
</div>
)
}
}
3. 兄弟组件传值

这里我们可以通过上面的思路去完成从A组件到B组件的传值通讯
第一步 SonA 通过子传父 传值给 APP
第二步 APP 拿到SonA的值,然后通过父传子把值传递给SonB这样整个通讯就完成了。
这里这种需求传值相信大家在项目中也不会这样写,没有观赏性 可读性,后面还会有更好的方法解决这个传值,我们就简单带过,哈哈哈。
4. 跨组件通信Context
在我们真实项目中,往往就会有这种嵌套的组件存在。如果通过父传子一层层的传下去,显然很繁琐,让人很头痛。
Context 就为我们提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
现在我们就通过Context把APP里的数据,让SonB SonC拿到。
// 1. 首先创建一个Context对象 导出 Provider 和 Consumer对象
/**
* @Provider Provider为其包裹的组件提供数据
* @Consumer Consumer包裹的组件可以拿到Provider提供的值
*/
const { Provider, Consumer } = createContext()
// 使用数据
function SonB() {
return (
<Consumer >
{value => <div>{value}</div>} // 从前从前有个人爱你很久,但偏偏风渐渐把距离吹得好远。
</Consumer>
)
}
function SonC() {
return (
<Consumer >
{value => <div>{value}</div>} // 从前从前有个人爱你很久,但偏偏风渐渐把距离吹得好远。
</Consumer>
)
}
function SonA() {
return (
<SonB/>
<SonC/>
)
}
// 提供数据
class App extends React.Component {
state = {
message: '从前从前有个人爱你很久,但偏偏风渐渐把距离吹得好远。'
}
render() {
return (
<Provider value={this.state.message}>
<div className="app">
<ComA />
</div>
</Provider>
)
}
}
6. 生命周期
在 React 中,每一次的状态改变都会导致页面视图的改变,都会经历两个阶段:render 阶段、commit 阶段。
class 组件实例的所有生命周期函数,都会在 render 阶段和 commit 阶段执行。
注意,只有类组件才有生命周期(类组件 实例化 函数组件 不需要实例化)。组件实例从被创建到被销毁的过程称为 组件的生命周期。
1. 挂载阶段
| 钩子函数 | 触发 | 作用 |
|---|---|---|
| constructor | 创建组件时,初始化的时候只执行一次 | 1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等 |
| render | 每次组件渲染都会触发 | 渲染UI(注意: 不能在里面调用setState() ) |
| componentDidMount | 组件挂载后执行,初始化的时候执行一次 | 发送网络请求 ,DOM操作 |
2. 更新阶段
| 钩子函数 | 触发 | 作用 |
|---|---|---|
| render | 每次组件渲染都会触发 | 渲染UI(与 挂载阶段 是同一个render) |
| componentDidUpdate | 组件更新后(DOM渲染完毕) | DOM操作,可以获取到更新后的DOM内容,不要直接调用setState |
3. 卸载阶段
| 钩子函数 | 触发 | 作用 |
|---|---|---|
| componentWillUnmount | 组件卸载 | 卸载时执行清理工作 |
7. Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。一套能够使函数组件更强大,更灵活的 "钩子"。
注意点:
- 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
- hooks只能在函数组件中使用
- 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用,下面是官方的解释。

1. useState
import { useState } from 'react'
function App() {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)名字可以任意起,注意语义化
// `useState` 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。<br/>
const [count, setCount] = useState(0)
// setCount是一个函数,参数表示`最新的状态值`
// 调用该函数后,将使用新值替换旧值
// 修改状态后,由于状态发生变化,会引起视图变化
return (
// 这里我们每次点击都会让 count + 1
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
注意:
- 只能出现在函数组件或者其他
hook函数中- 不能嵌套在
if/for其它函数中(react按照hooks的调用顺序识别每一个hook)
useState 里的回调函数的参数
// 语法
const [name, setName] = useState(()=>{
// 计算逻辑代码
// return '计算之后的name初始值'
})
// 例子
function Son(props) {
const [count, setCount] = useState(() => {
// 初始化的值是父组件传来的 10
return props.count
})
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
)
}
function App() {
return (
// <></>幽灵节点
<>
<Son count={10} />
</>
)
}
2. useEffect
// useEffect 即 副作用
// 副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。
// 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM,ajax请求)
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
// 初始化的时候会执行
useEffect(()=>{
console.log(`当前已点击了${count}次`);
})
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
3. 依赖项执行时机
在我们没有对依赖项做任何处理的时候,组件首次渲染会执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行。
1. 没有添加依赖项
useEffect(()=>{
// 首次渲染以及状态更改引起组件更新时都会重新执行
console.log('副作用执行')
})
2. 添加空数组
useEffect(()=>{
// 只在首次渲染时执行一次
console.log('副作用执行')
},[])
3. 添加特定依赖项
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('杰伦')
useEffect(() => {
// name 变化时是不会执行的
console.log('副作用函数在首次渲染时执行,仅在依赖项count发生变化时重新执行')
}, [count])
return (
<>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
<button onClick={() => { setName('周董') }}>{name}</button>
</>
)
}
需要注意的是: 我们使用 create-react-app创建的项目,开发的时候
useEffect初始化会调用两次,这里不用太关心。它是因为React.StrictMode严格模式导致的,打包后就会正常了。如果只在介意,可以在index.js删除,改成root.render(<App />)。
4. 清理副作用
先看代码
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => {
// 用来清理副作用的事情
clearInterval(timerId)
}
}, [count])
return (
<div>
{count}
</div>
)
}
当我们快速点击时,就会出现上一个 setInterval 还没有结束的情况,这个时候我们就用到了我们的useEffect 副作用。
在副作用函数中的末尾
return一个新的函数,在新的函数中编写清理副作用的逻辑。 它的执行机制:
- 组件卸载时自动执行
- 组件更新时,下一个
useEffect副作用函数执行之前自动执行
4. 发送请求
// bad
useEffect(async ()=>{
// 不可以直接在useEffect的回调函数外层直接包裹 await ,因为异步会导致清理函数无法立即返回
const res = await axios.get('api/getUserInfo')
console.log(res)
},[])
// good
useEffect(()=>{
async function getUser(){
const res = await axios.get(''api/getUserInfo'')
console.log(res)
}
},[])
5. useRef
使用useRef获取真实dom或组件实例的方法
function App() {
const h1Ref = useRef(null)
useEffect(() => {
console.log(h1Ref)
},[])
return (
<div>
<h1 ref={ h1Ref }>this is h1</h1>
</div>
)
}
这里需要注意: 我们日常中需要通过
Ref获取组件的实例,但是只可以去获取类组件的实例,因为函数组件没有实例,不能使用Ref获取。类组件的写法,真的很头疼。这个时候官方为我们提供了forwardRefuseImperativeHandle解决了useRef获取函数组件的问题。
这里开发者需要注意官方的提示:forwardRef 是一个破坏性的更改,可能会引起依赖旧行为的应用和其他库崩溃!

import React,{useRef,forwardRef} from 'react'
// 子组件
const Son = forwardRef((props, ref) => {
// useImperativeHandle,将 ref 和 方法(此处是onChange)传给父组件
useImperativeHandle(ref, () => {
return {
onChange: () => {
console.log('测试');
}
}
},[])
return (
<>
<div>
<input type="text" defaultValue={props.value} ref={ref} />
<button onClick={() => console.log(ref.current)}>点击打印ref</button>
</div>
</>
)
})
// 父组件
const App = () => {
const sonRef = useRef(null)
useEffect(()=>{
sonRef.current.onChange() ----测试
},[])
return (
<>
<Son ref={sonRef} value='给子组件的value值' />
</>
)
}
6. useContext
上面组件通讯时我们讲到context,这里说一下hooks下的context使用方式。
import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()
function SonA() {
return <div>SonA <SonB/> </div>
}
function SonB() {
// 底层组件通过useContext函数获取数据
const value = useContext(Context)
return <div>SonB {value}</div>
}
function App() {
return (
// 顶层组件通过Provider提供数据
<Context.Provider value={'给SonB的数据'}>
<>
<SonA/>
</>
</Context.Provider>
)
}
看到这里我们的React的基础就到这里了,对比起来Vue的同学们,其实学习成本并不是很大。
我们从学习它的语法在到它的思想,需要一个循环渐进的过程。可以从GitHub上拉一些React项目,或者自己试着拉一条新分支,改造现有的Vue项目。
两个框架之间有很多相似的地方,各有千秋。不能说谁比谁好,两大框架都很优秀。
性能对比上看过krausest的框架对比测试,Vue3.2是要略高于React18.2的,但是呢这个也要取决最终的前端项目 前端工程化 开发人员的优化 还有整体业务和项目大小。所以这些我们都可以忽略不计,都是很优秀的框架。
不过大多数大公司都是有React要求的,Vue的岗位虽然更多一些,但是React的薪资开的普遍要高于Vue一些的。后续的话还会接着更新 记录我的React的过渡之路。
建议大家在熟悉
Vue的时候,同时也要掌握React。本文是自己从Vue开发者到React开发者的一个转变过程,真心觉得React的一些思想很不错,写起来的感觉也挺好的。写下本文,希望能帮助到Vue的开发者快速上手React。谢谢大家!
转载自:https://juejin.cn/post/7143134220624822279