2023 Vue开发者的React入门

React​ 新文档重新设计了导航结构,让我们更加轻松地找到所需的文档和示例代码 不仅提供了基础知识的介绍,还提供了更加详细的原理介绍和最佳实践,包括:React​ 组件的设计哲学、React Hooks的原理和用法等。

React​ 新文档重新设计了导航结构,让我们更加轻松地找到所需的文档和示例代码 不仅提供了基础知识的介绍,还提供了更加详细的原理介绍和最佳实践,包括:React​ 组件的设计哲学、React Hooks的原理和用法等。

Vue和React都是流行的JavaScript框架,它们在组件化、数据绑定等方面有很多相似之处

本文默认已有现代前端开发(Vue)背景,关于组件化、前端路由、状态管理概念不会过多介绍

0基础建议详细阅读 Thinking in React-官方文档 了解React的设计哲学

  • React 新文档- https://react.dev
  • React 中文文档(翻译中)- https://react.jscn.org

经过本文的学习让没开发过React项目的Vue开发者可以上手开发现有的 React 项目,完成工作需求开发

React 新文档

React新文档重新设计了导航结构,让我们更加轻松地找到所需的文档和示例代码 不仅提供了基础知识的介绍,还提供了更加详细的原理介绍和最佳实践,包括:React组件的设计哲学、React Hooks的原理和用法等

并且提供了在线编辑和运行的功能,方便开发者进行测试和实验

👇 基于函数组件

图片

初学可以只学函数组件,You Don't Need to Learn Class Components

图片

👇interactive sandboxes可交互沙箱,边做边学

图片

Fork可以单独打开页签

图片

JSX 与 SFC

  • 在Vue中我们使用单文件组件(SFC)编写组件模版 (虽然Vue也支持使用JSX, 但是更鼓励使用SFC)
  • 在React中,JSX(JavaScript XML)是一种将HTML语法嵌入到JavaScript中的语法扩展。它可以使得我们在JavaScript代码中轻松地定义组件的结构和样式,从而提高代码的可读性和可维护性

虽然React和Vue在组件定义方式上存在差异,但是它们的组件化思想是相似的

根节点

👇 Vue

<template>
  <div>同级节点1</div>
  <div>同级节点2</div>
</template>

👇 React

const App = (
  <>
    <div>同级节点1</div>
    <div>同级节点2</div>
  </>
)

const App = (
  <React.Fragment>
    <div>同级节点1</div>
    <div>同级节点2</div>
  </React.Fragment>
)

条件渲染

👇 Vue

<div v-if="show">条件渲染</div>
<div v-show="show">条件渲染</div>

👇 React

{
  show ? <div>条件渲染</div> : null
}

循环语句

👇 Vue

<ul>
  <li v-for="i in list" :key="i.id">{i.name}</li>
</ul>

👇 React

<ul>
  { list.map(i => <li key={i.id}>{i.name}</li>) }
</ul>

表单绑定

👇 Vue

<input v-model="value"/>

👇 React

<input value={value} notallow={onChange}/>

可以看出React的JSX语法学习记忆成本更低一点(当然Vue也不复杂),Vue更语法糖一些

单向数据流与双向绑定

在Vue中,我们使用v-bind、v-modal对数据进行绑定,无论是来自用户操作导致的变更,还是在某个方法里赋值都能够直接更新数据,不需要手动进行update操作

this.data.msg = '直接修改数据后视图更新'

在React中,数据流是单向的,即从父组件传递到子组件,而不允许子组件直接修改父组件的数据。需要调用set方法更新,当React感应到set触发时会再次调用render对dom进行刷新

msg = "Hello" // ❌ 错误写法

setMsg('Hello'); // ✅ 来自hooks的set写法 后面会介绍

🤔Vue本质上底层也是单向的数据流,只不过对使用者来说看起来是双向的,如v-model本质也要set

React Hooks

React Hooks是React 16.8版本中引入的特性,它可以让我们在函数组件中使用状态(state)和其他React特性

Hooks本质是一些管理组件状态和逻辑的API,它允许开发者在函数式组件中使用状态、副作用和钩子函数,可以更加方便地管理组件状态、响应式地更新DOM、使用上下文等

在没有Hooks前,函数组件不能拥有状态,只能做简单功能的UI(静态元素展示),大家使用类组件来做状态组件

因为函数组件更加匹配React的设计理念UI = f(data),也更有利于逻辑拆分与重用的组件表达形式,为了能让函数组件可以拥有自己的状态,Hooks应运而生

组件的逻辑复用

在Hooks出现之前,React先后尝试了mixins混入,HOC高阶组件,render-props等模式。但是都有各自的问题,比如mixins的数据来源不清晰,高阶组件的嵌套问题等等

class组件自身的问题

class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等

useState

参数接受一个默认值,返回[value, setValue]的元组(就是约定好值的JavaScript数组),来读取和修改数据

👇 不使用Hooks的静态组件,当点击修改数据,视图不会重新渲染

function App() {
  let count = 1
  const add = () => count++ // 不会触发重新渲染

  return <div onClick={add}>{count}</div>
}

👇 使用useState

import { useState } from 'react'

function App() {
  let count = 1
  const [proxyCount, setProxyCount] = useState(count)
  const add = () => setProxyCount(proxyCount+1)

  return <div onClick={add}>{proxyCount}</div>
}

我们分析一下触发数据修改的函数组件行为:

组件会第二次渲染(useState返回的数组第二项setProxyCount()被执行就会触发重新渲染)

  1. 点击按钮,调用setProxyCount(count + 1)修改状态,因为状态发生改变,所以,该组件会重新渲染
  2. 组件重新渲染时,会再次执行该组件中的代码逻辑
  3. 再次调用useState(1),此时React内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为2
  4. 再次渲染组件,此时,获取到的状态count值为2

👆 也就是触发重新渲染会让useState也重新执行,但是useState的参数(初始值)只会在组件第一次渲染时生效

每次的渲染,useState获取到都是最新的状态值,React 组件会记住每次最新的状态值

useEffect

上面我们分析触发组件重新渲染就可以发现,React的函数组件没有具体的生命周期钩子

React更希望我们把组件当作函数,而去关注函数的函数的副作用,而没有实例化过程的钩子

useEffect就可以很好的帮助我们达到我们想要的效果:

  1. 处理组件第一次渲染时的回调,类似Vue中的mounted
// 第二个参数传一个空数组,表示没有依赖,只会在第一次渲染时执行
useEffect(() => {
  alert('mounted');
}, [])
  1. 通过依赖变更触发的钩子函数,只要有一项依赖发生变化就执行,类似Vue中的watch
function Comp({ title }) {
  const [count, setCount] = useState(0);
  // 第二个参数指定一个数组,放入你想监听的依赖:
  useEffect(() => {
    console.log('title or count has changed.')
  }, [title, count])
}

原则上,函数中用到的所有依赖都应该放进数组里

  1. 组件卸载时执行内部return的函数
import { useEffect } from "react"

const App = () => {

  useEffect(() => {
    const timerId = setInterval(() => {
      console.log('定时器在运行')
    }, 1000)

    return () => { // 用来清理副作用的事情
      clearInterval(timerId)
    }
  }, [])

  return <div>内部有定时器</div>
}

我们常见的副作用1. 数据请求ajax发送 2. 手动修改dom 3. localstorage操作

自定义 Hooks

获取滚动距离y:

import { useState, useEffect } from "react"

export function useWindowScroll () {
  const [y, setY] = useState(0)

  useEffect(() => {
    const scrollHandler = () => {
      const h = document.documentElement.scrollTop
      setY(h)
    }
    window.addEventListener('scroll', scrollHandler)
    return () => window.removeEventListener('scroll', scrollHandler)
  })

  return [y]
}

使用:

const [y] = useWindowScroll()
return <div>{y}</div>

图片

封装的Hooks名称也要用use开头(这是一个约束)

状态管理

React的状态管理有很多,入门可以暂时不考虑

或者已有项目使用什么再学习即可,和Vuex整体思路差不多

tic-tac-toe 井字棋游戏

最后我们跟着React官方文档实现一个井字棋游戏来巩固知识点

使用Vite创建项目

图片

pnpm create vite react-tic-tac-toe --template react
cd react-tic-tac-toe
pnpm i
pnpm dev

👇vite.config.js非常简洁

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

👇 修改入口文件main.jsx

import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

👇util.js计算当前棋局是否有获胜

// 计算当前棋局是否有获胜
export function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

👇Square.jsx正方形按钮组件

// 正方形按钮组件
export default function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

👇App.jsx

import { useState } from 'react';
import { calculateWinner } from './util.js'
import Square from './Square'

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = 'X';
    } else {
      nextSquares[i] = 'O';
    }
    // 执行父组件的落子事件
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    // 胜利提示
    status = '获胜方是: ' + winner;
  } else {
    // 下一步提示
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];

  // 棋盘落子
  function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    // 记录落子历史,用于恢复棋局
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  // 恢复棋局到第几步
  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  // 历史落子列表按钮展示,用于点击恢复棋局
  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

图片

深入学习任一前端框架都不容易,让我们一起加油吧!

参考资料

  • React 新文档- https://react.dev
  • React 中文文档(翻译中)- https://react.jscn.org
  • 给 Vue 开发的 React 上手指南- https://juejin.cn/post/6952545904087793678
  • 无缝切换?从Vue到React- https://zhuanlan.zhihu.com/p/609120596
  • How to Learn React in 2023- https://www.freecodecamp.org/news/how-to-learn-react-in-2023

图片

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月19日 17:00
下一篇 2023年5月19日 17:04

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信