当公司要求你必须会 React,Vueer 不得不学

本文介绍了 Vue 用户如何适应 React,从渲染 HTML、条件和列表渲染、事件处理、组件通信等方面,深入探讨 React 的核心概念。文章还讨论了受控与非受控组件、生命周期、Portals、Context、异步组件以及 Hooks(如 useState、useEffect、useCallback)。通过实例解析,帮助开发者理解 React 的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

作为一个重度 Vue 使用者,在学习使用React时难免有些不适应,甚至有点急躁。

但时事变迁,现在不学 React,找工作真的很难呀。

所以纵使 React 再不好学,也要熟练运用它。

毕竟编程就是这样,有些语言可能不太好理解,不过既然你不是框架开发者,只能适应它。

思想方面

学 React 的时候,就先把 Vue 给忘掉。

不要急,慢慢来。

正文

如果看不懂,可以跳着看,先把能看懂的吸收掉。

渲染 HTML 字符串

function Test() {return (<><p dangerouslySetInnerHTML={{ __html: '<i style="color:red;">123</i>' }} /></>)
} 

条件渲染、列表渲染

不像 Vue 有 v-if、v-for 指令,React 直接使用 js。

React 会自动将数组展开渲染。

export function Login(props: any) {const [show, setShow] = useState(false)const [list, setList] = useState(['小梅', '小军', '小强'])return (<>{show ? '显示的文本' : '隐藏的文本'}<br />{list}</>);
} 

事件

传递函数。在下面的例子中,点击父组件按钮和Test组件按钮都会打印点我了

如果需要传参,,请用箭头函数。。

这是个父组件:

export function Login(props: any) {function handleClick() {console.log('点我了');}return (<><button onClick={handleClick}>父组件</button><Test xx={handleClick} /></>);
} 

这是个子组件:

export function Test(props: any) {return <button onClick={props.xx}>Test组件</button>
} 

受控组件

使用 state 来控制表单的输入显示。

class MyComponent extends React.Component{state = {inputVal: 'input'}handleInput = e => {this.setState({inputVal: e.target.value})}render() {return (<><p>{this.state.inputVal}</p><input type="text" value={this.state.inputVal} onInput={this.handleInput} /></>)}
} 

非受控组件

表单的输入显示不受 state 控制。而是取得表单元素的 dom,来获取或设置它们的值。

import { useRef } from "react";

export function Login(props: any) {const nameInput = useRef<HTMLInputElement>(null)function handleClick() {console.log(nameInput.current?.value);}return (<>姓名:<input ref={nameInput} type="text" /><br /><button onClick={handleClick}>提交</button></>);
} 

父子组件通信

父向子传递数据,通过 prop 传。子向父传递数据,也是给子传递一个函数,子调用函数来传递修改父的数据。

父组件

// 父组件
class MyComponent extends React.Component{state = {count: 99}render() {return (<div><ChildComponent count={this.state.count} changeCount={this.changeCount} /></div>)}// 接收子组件传递的count来修改自身的countchangeCount = count => {this.setState({count})}
} 

子组件

// 子组件
class ChildComponent extends React.Component {render() {return <div>{this.props.count}<button onClick={this.addCount}>addCount</button></div>}addCount = () => {// 获取父组件传递的propsconst { changeCount, count } = this.props// 调用父组件的changeCount方法changeCount(count + 1)}
} 

实现插槽

React 实现像 Vue 一样的默认插槽、具名插槽、作用域插槽,其实就是利用 React 的 prop 什么都能传的特点。

默认插槽,利用 this.prop.children

export class Login extends React.Component {render() {return (<><Child>我是光</Child></>)}
}

function Child(props: any) {return (<div><div>我显示父组件传进来的内容:</div><br />{props.children}</div>)
} 

具名插槽。父组件不一定传函数,也可以直接渲染标签,子组件接收就可以了。

export class Login extends React.Component {render() {return (<><Child renderTitle={<div>这。。。</div>}>我是光</Child></>)}
}

function Child(props: any) {return (<div>{props.renderTitle ? props.renderTitle : <div>默认插槽</div>}</div>)
} 

作用域插槽。子组件可以利用函数参数把数据带给父组件。

 render() {return (<><Child renderItem={(item: string, index: number) => {return <li>{index+1}号,{item}</li>}}></Child></>)}
}

function Child(props: any) {const arr = ['姚明','科比']return (<div>{arr.map((item, index) => {return props.renderItem ? props.renderItem(item, index) : <div>空</div>})}</div>)
} 

setState

使用 setState,要注意不能直接修改原数据。当需要修改层级很深的对象时,为了方便可以使用 immer 库。(比较流行)

this.setState({userName: '小花',userAge: 19
}) 

生命周期

React 常用的生命周期函数有 componentDidMount、shouldComponentUpdate。 componentDidMount 和 Vue 的 mounted 钩子差不多。shouldComponentUpdate 让用户能自主决定一个组件是否需要渲染。

class MyComponent extends React.Component{constructor(props) {super(props) }shouldComponentUpdate (nextProps, nextState, nextContext) {console.log(nextState.count, this.state.count)if (nextState.count !== this.state.count) {return true // 允许渲染,会往下执行render}return false // 不渲染,不执行render}render() {return ...}
} 

React Portals

当一个父元素设置 overflow: hidden 的时候,子元素超出父元素边界就会看不见。这对于弹框组件不太友好。

比如父组件:

export function Login() {const [show, setShow] = useState(false)function handleClick() {setShow(!show)}return (<div className='father'><Model show={show} /><button onClick={handleClick}>打开弹框</button></div>);
} 
.father {overflow: hidden;position: fixed;left: 50%;top: 50%;transform: translate(-50%, -50%);width: 250px;height: 250px;border: 1px solid #000;
} 

然后子组件:

function Test(props: any) {return (<>{props.show ? <div className='model'>你好,我是弹框。</div> :<div>啥也不是</div>}</>)
} 
.model {position: fixed;left: 50%;top: 50%;/* transform: translate(-50%, -50%); */background-color: red;color: #fff;width: 250px;height: 250px;border: 1px solid #000;
} 

就会出现这样的情况。

将子组件改为 Portal 后,问题立马得到解决。

import React from 'react';
import ReactDOM from "react-dom";
import './test.css'

function Test(props: any) {return (<>{props.show ? ReactDOM.createPortal(<div className='model'>你好,我是弹框。</div>, document.body):<div>啥也不是</div>}</>)
}

export default React.memo(Test) 

React Context

和 Vue 的 Provide/Inject 作用是一样的,但是写的东西更多一点。

import { createContext,useContext } from "react";

const ThemeContext = createContext('');

export function Login() {return (<ThemeContext.Provider value='喔喔喔'><div className='father'><Test /></div></ThemeContext.Provider>);
}

function Test(props: any) {const theme = useContext(ThemeContext);return (<><div>{theme}</div></>)
} 

异步组件

简单记录了一下用法。

import React from "react";

export function Login() {return (<div className='father'><React.Suspense fallback={<div>loading...</div>}><Lazyy /></React.Suspense></div>);
}

const Lazyy = React.lazy(() => new Promise((resolve) => {setTimeout(() => {const a = import('../../components/test')// @ts-ignoreresolve(a)}, 1000);
})) 

useEffect

说实话,我当时对这个 useEffect 是一点也没搞明白,什么玩意儿!但要是理解了 React 函数式组件的渲染逻辑,就不会那么懵逼了。

React 函数式组件不像 class 组件,假设你在函数式组件里定义了一个方法,那么每次重新渲染时都会定义一个新的方法,与之前方法的引用并不相同。

而在 class 组件里定义一个方法,它的引用是不会变的,重新渲染也只是重新执行了 render 方法。

如果你正在学习 React,并且也对 useEffect 充满疑惑,一定要看看 Dan 写的这篇文章!useEffect 完整指南

import {useState } from "react";

// let a = 1

export function Login(props: any) {const [count, setCount] = useState(0); function say () {let a = 1console.log('Hello Vue and React, are you ok ?', a ++);}say()return (<><button onClick={() => setCount(count + 1)}>加1,count:{count}</button></>);
} 

useState、useMemo、useCallback、React.memo

这里想想阐述的是,每次 React 改变state,函数式组件都会重新执行一遍,它的子组件也会跟着重新执行一遍。

如果想让它的子组件不会重新执行一遍,可以使用 React.memo 包裹子组件,它会使用 Object.is 来对比前后的 prop 是否相同, 不同就会重新渲染子组件。

在这里,我写了一个名为 Login 的组件(名字不重要,jy),它使用到了 Test 组件。

import { useCallback, useMemo, useState } from "react";
import Test from "components/test";

export function Login(props: any) {const [count, setCount] = useState(0); const handleClick = useCallback(() => {setCount(count + 1);}, [count]);// Test 组件执行一遍const data = useMemo(() => ({lalala: count}), [])// Test 组件首次会执行一遍,之后每次 count 加1,Test 组件都会执行一遍// const data = useMemo(() => ({// lalala: count// }), [count])// Test 组件首次会执行一遍,在 count 为3或4的时候会执行一遍// const data = useMemo(() => ({// lalala: count// }), [count === 3])return (<><button onClick={() => handleClick()}>加1,count:{count}</button><Test suibian={data} /></>);
} 

Test 组件如下

import Reactfrom 'react';

function Test(props: any) {console.log('Test函数组件执行了一遍');return <button>Test</button>
}

export default React.memo(Test) 

useCallback

现在我只有一个 Login 组件(不要在意名字,jym)。

为什么出现下面这种情况?

第一种情况依赖是个空数组,useCallback 它会将传入的函数进行保存,且它永远不会更新。 再由于闭包,传入的函数里所获取到的 count 值永远都是 0。所以每次点击按钮传入 setCount 的值其实都是1。

第二种情况以为传入了 count 依赖,在 count 变化后,useCallback 里的函数也会重新定义,拿到本次执行 Login 函数时定义的 count 值。 此时的 count 值为最新值。

import { useCallback, useState } from "react";

export function Login(props: any) {const [count, setCount] = useState(0); // 点击按钮后,count 加1,之后永远都为1了。const handleClick = useCallback(() => {setCount(count + 1);}, []);// 每次点击按钮,count 都会加1// const handleClick = useCallback(() => {// setCount(count + 1);// }, [count]);return (<><button onClick={() => handleClick()}>加1,count:{count}</button></>);
} 

React 实现计算属性

使用类的 getter 来实现计算属性,不过实现不了缓存。

class Example extends Component {state = {firstName: '',lastName: '',};// 通过getter而不是函数形式,减少变量get fullName() {const { firstName, lastName } = this.state;return `${firstName} ${lastName}`;}render() {return <Fragment>{this.fullName}</Fragment>;}
} 

如果要实现缓存,可以使用 memoize-one 库,它本质是一个高阶函数,会对传入的参数作前后对比。如果与上次传入的参数相同,就返回上次缓存的结果。 否则根据新的入参重新计算返回值。

import memoize from 'memoize-one';
import React, { Fragment, Component } from 'react';

class Example extends Component {state = {firstName: '',lastName: '',};// 如果和上次参数一样,`memoize-one` 会重复使用上一次的值。getFullName = memoize((firstName, lastName) => `${firstName} ${lastName}`);get fullName() {return this.getFullName(this.state.firstName, this.state.lastName);}render() {return <Fragment>{this.fullName}</Fragment>;}
} 

如何用 React Hooks 实现计算属性呢?使用 useMemo 钩子。

import React, { useState, useMemo } from 'react';

function Example(props) {const [firstName, setFirstName] = useState('');const [lastName, setLastName] = useState('');// 使用 useMemo 函数缓存计算过程const renderFullName = useMemo(() => `${firstName} ${lastName}`, [firstName,lastName,]);return <div>{renderFullName}</div>;
} 

useReducer

看下一个简单的例子,了解下其用法。

import React, { useReducer } from "react";
const initState = {userName: '匿名',
}

const reducer = (state: any, action: any) => {switch (action.type) {case 'add':return {...state,userName: state.userName + '+'}case 'del':return {...state,userName: state.userName + '-'}default:return {...state,userName: state.userName + '*'}}
}

export function Login() {const [state, dispatch] = useReducer(reducer, initState)function handleClick() {dispatch({type: 'add'})}return (<>hello, {state.userName}<button onClick={handleClick}>按钮</button></>)
} 

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值