从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
获取。类组件的写法,真的很头疼。这个时候官方为我们提供了forwardRef
useImperativeHandle
解决了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