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

前言

作为一个重度 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来修改自身的count
  changeCount = count => {
    this.setState({
      count
    })
  }
}
复制代码

子组件

// 子组件
class ChildComponent extends React.Component {
    render() {
        return <div>
          {this.props.count}
          <button onClick={this.addCount}>addCount</button>
        </div>
    }
    addCount = () => {
        // 获取父组件传递的props
        const { 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-ignore
    resolve(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 = 1
    console.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 React  from '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>
    </>
  )
}
复制代码

结尾

这一篇文章,并没有特别的去解释某些东西,更多的是给出一个例子,代码的一个 demo。

对于初学一点没接触过 React 的同学可能有点看不懂。

不过遇到不懂的东西,就打开浏览器另外一个窗口去搜一搜,再回过头来看写的,也是一种办法。

最后希望与大家一起努力,实现自己的梦想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值