项目需要开发一个web平台,已有 HTML + CSS + JavaScript 的一定基础

本文主要是快速入门react的笔记,仅了解核心的概念和语法,足够后续更改开源项目即可。学习过程主要参考下面的视频和官方文档

基本概念

  1. react是什么

    我们平常想要在Web网站上展示数据的时候,需要

    (1)发送请求获取数据

    (2)处理数据(过滤、整理格式等)

    (3)操作DOM呈现页面

    在我们操作DOM呈现页面的时候,通常需要DOM-API来操作UI,繁琐、效率低

    1
    2
    3
    document.querySelector('.btn')
    document.getElementById('app')
    document.getElementsByTagName('span')

    react就是帮助我们进行(3),可以更加方便的操作DOM,将数据渲染为HTML视图

  2. react的作用

    原生 JavaScript 的缺点

    • 原生JavaScript通过DOM-API操作DOM,繁琐且效率低
    • 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
    • 原生JavaScript没有组件化编码方案,代码复用率低。

    react:

    • 组件化模式、声明式编码,提高开发效率及组件复用率
    • react native可以通过react语法进行移动端开发
    • 使用虚拟DOM和Diffing算法,尽量减少与真实DOM的交互,防止了重绘重排,实现视图的高效更新

    虚拟DOM的理解:

    尚硅谷React教程 16:45-25:26

  3. jsx

    react中定义的一种JavaScript 的扩展语法(即react独有的模版语法),将HTML标签和JS代码混合使用。

    例如:

    1
    2
    3
    const element = <h1 className="foo">Hello, world</h1>;
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(element);
  4. 状态

    状态是组件中用于存储和控制数据的对象,当状态发生变化时,组件会重新渲染以反映新的状态。这使得React组件可以动态地响应用户交互和其他事件。

    即产生了数据的更新、交互

jsx基本语法

  1. 组件

    1)定义

    React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。

    React 组件在形式上表现为:返回标签的 JavaScript 函数

    • React 组件必须以大写字母开头
    • React 组件都是以函数形式表示的

    2)示例

    自定义 MyButton 组件

    1
    2
    3
    4
    5
    function MyButton() {
    return (
    <button>I'm a button</button>
    );
    }

    所定义的组件可以嵌套到其他的组件,注意这个应用方法 <MyButton /> ,进行了自闭和

    JSX 比 HTML 更加严格,必须闭合标签,如 <br />

    1
    2
    3
    4
    5
    6
    7
    8
    export default function MyApp() {
    return (
    <div>
    <h1>Welcome to my app</h1>
    <MyButton />
    </div>
    );
    }
    • 父组件:这里可以认为 MyApp 是一个 父组件,将每个 MyButton 渲染为一个“孩子”。这是 React 的神奇之处:你可以只定义组件一次,然后按需多处和多次使用。

    3)注意事项

    • 不能嵌套组件的定义,即在一个组件中不能直接定义另一个组件
    • 当子组件需要使用父组件的数据时,你需要 通过 props 的形式进行传递
  2. 每一个组件只能一个根元素

    1)原因

    • 不管是vue还是react,模板都将被编译为render函数,而函数的返回值只能是一个,所以如果不用单独的根节点包住,就会并列返回多个返回值,这在js中是不允许的。
    • 除了这一点,还有一个主要是原因是,react和vue都将把模板的内容转换为对应的元素,最后建立起虚拟dom树,而树状结构只能有唯一的根节点,这样在后续的虚拟dom数据有变化时,可以检查到具体更改的位置。如果有多个根节点,则不能明确到底要在哪个树上查找更新。

    2)如何返回多个元素?使用Fragment将它们包裹到一个共享的父级中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function App(){
    const list = [
    {id : 1, name : 'a'},
    {id : 2, name : 'b'},
    {id : 3, name : 'c'}
    ]

    // 像这样在组件中并列返回多个根元素是不可行的
    // const listConent = list.map(item => (
    // <li key = {item.id}>{item.name}</li>
    // <li>----------------------</li>
    // ))

    const listConent = list.map(item => (
    <Fragment key = {item.id}>
    <li key = {item.id}>{item.name}</li>
    <li>----------------------</li>
    </Fragment>
    ))

    return(
    <ul>{listConent}</ul>
    )
    }

    export default App;

    Fragment 的详细用法:React中文文档 | Fragment

  3. 标签属性设置

    • 使用 className 来设置 CSS 的class
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import image from './logo.svg'
    function App() {
    const imgData = {
    className: 'small',
    style: {
    width: 200,
    height: 200,
    backgroundColor: 'grey'
    }
    }

    return (
    <div>
    <img
    src={image}
    alt=""
    {...imgData}
    />
    </div>
    )
    }
    export default App;

react hook

  1. 基本概念

    react的思想在于将组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。

    react Hooks 是一些可以让你在函数组件中“钩入” React 状态和生命周期功能的特殊函数。它们可以解决一些 class 组件中常见的问题,并且使代码更加简洁和可读。

    因为所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。

    react hook的功能在某些方面比较类似于库函数,对一些特定的功能进行了封装,可以更加方便地重复使用。但是不同的是库函数式独立的,不依赖于特定的上下文或框架,例如,数学库函数(如Math.sqrt)可以在任何 JavaScript 代码中使用。但是react hook只能在React 函数组件或自定义 Hook 中使用。

  2. React 内置 Hook

    1)useState()

    • 定义:用于在函数组件中进行状态变更

    • 语法:

      • content:需要渲染的内容

      • setContent:用于更新状态的函数, 后续调用该函数进行状态更新操作。

        注意在对对象进行更新的时候,一定要把所有的键值对都写上去,即使没有改变也要写,因为这里的逻辑是用新的对象直接覆盖掉原有的对象,如果没有将不变的键值对写上去会直接失去这些键值对。

        将所有属性都写上去又比较繁琐,所以我们可以在前面加上 ...content,表示将content的所有属性先写在这里,后面再写上需要改变的键值对来覆盖掉前面的值。

      • useState()内接收状态的初始值。

        这里的初始内容可以为变量、对象的形式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const [content, setContent] = useState("初始内容");
      const [content,setContent] = useState({
      title: '默认标题',
      content: '默认内容'
      })

      // setContent后面为更新后的内容
      setContent('新内容')
      function handClick(){
      setContent({
      ...content,
      content: '新内容'
      })
      }
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import React, { useState } from "react";

      export default function Button() {
      const [buttonText, setButtonText] = useState("Click me, please");

      function handleClick() {
      return setButtonText("Thanks, been clicked!");
      }

      return <button onClick={handleClick}>{buttonText}</button>;
      }

初始化应用

  1. 环境要求

    • 安装 Node.js。Node 包括 npm(Node 程序包管理器)和 npx(Node 程序包运行器)

      • 也可以使用Yarn作为npm的替代方案
    • 设置npm的镜像源

      • 查看:

        1
        npm config get registry
      • 更改为淘宝源

        1
        npm config set registry https://registry.npmmirror.com
  2. 初始化应用

    1
    npx create-react-app reacttest
    • 输入命令后会在 reacttest 文件夹下面构建好应用程序的基础架构

      注意项目名字只能是小写,不能大小写混合

    • 处理完成之后,可以 cd 到 reacttest 文件夹下,然后键入 npm start 命令并回车,先前由 create-react-app 创建的脚本会启动一个本地服务 localhost:3000,并打开你的默认浏览器来访问这个服务。成功启动浏览器的话,你的浏览器上会显示如下画面,表示初始化成功

      image-20240718165830913

  3. 文件结构

    • src文件夹: 存放React 应用源码的目录。

      可以保留该目录下的这两个文件,其他文件都不重要,可以直接全部删除

      • index.js :入口文件

      • APP.js :根组件文件

        react中的一个重要思想:其包括两种组件形式

        • 函数形式(主流)
        • 类形式
  4. APP.js组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import logo from "./logo.svg";
    import "./App.css";

    function App() {
    return (
    <div className="App">
    <header className="App-header">
    <img src={logo} className="App-logo" alt="logo" />
    <p>
    Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
    className="App-link"
    href="https://reactjs.org"
    target="_blank"
    rel="noopener noreferrer">
    Learn React
    </a>
    </header>
    </div>
    );
    }
    export default App;

    可以看到组件文件由三部分组成:import语句、APP组件函数和底部的export语句

    • import语句:允许在此脚本中使用其他文件中的代码

      在这里引入我们需要的本地文件,包括css、图片等

    • APP组件函数

      返回一个JSX表达式,该表达式定义了浏览器最终需要渲染的DOM

    • export语句:在 App.js 文件的最底部

      export default App 语句使得 App 组件能被其他模块使用。

    **插值的实现:**通过括号 + 变量名称。可以在标签内容和属性部分进行插值

    • 注意使用{}进行插值的方式
    • 注意jsx的语法,在给divContent赋值的时候并不需要使用引号,直接写标签即可
  5. index.js入口文件

渲染方式

  1. 条件渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function App() {
    const divTitle = '标签标题'
    const flag = true

    let divContent = null
    if (flag){
    divContent = <span>flag为true</span>
    } else{
    divContent = <p>flag为false</p>
    }

    return (
    <div title = {divTitle}>{divContent}</div>
    );
    }

    export default App;
  2. 数组渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function App(){
    const list = [
    {id : 1, name : 'a'},
    {id : 2, name : 'b'},
    {id : 3, name : 'c'}
    ]
    const listConent = list.map(item => (
    <li key = {item.id}>{item.name}</li>
    // 下面这样会报warning,没有key,没有唯一性
    //<li>{item.name}</li>
    ))

    return(
    <ul>{listConent}</ul>
    )
    }

    export default App;
    • 在react中遍历数据时,推荐在组件中使用 key 属性。这里的数组最好要设置一个key,保证当前元素的唯一性(即上面的id,一般在后端设置好这样的形态再返回到前端)

      这样处理的原因是可以适配diff算法,更高效地创建react元素树以更新UI

      详情可以参考:React总结:一文知React

  3. 响应事件与状态更新

    注意要使用 useState 进行状态的更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { useState } from 'react';
    function App(){
    const [content,setContent] = useState("初始内容")

    function handClick(){
    setContent("新内容")
    }

    return (
    <>
    <div>{content}</div>
    <button onClick={handClick}>按钮</button>
    </>
    )
    }

    export default App;

组件间数据共享:props

父组件传递数据给子组件

  1. 定义:React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它

  2. 语法:

    • 预定义 props 后将其传递给子组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 父组件Profile将person和size传递给子组件Avatar
      export default function Profile() {
      return (
      <Avatar
      // 这里为什么使用双括号:第一个括号表示传递,第二个括号是表示对象
      person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
      size={100}
      />
      );
      }
    • 在子组件中读取 props

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 在子组件中读取 person 和 size
      function Avatar({ person, size = 100 }) {
      return (
      <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
      />
      );
      }
      • size = 100 :设置Prop的默认值
  3. 将 JSX 作为子组件传递(组件插槽)

    • 可以直接将JSX传递,父组件将在名为 children 的 prop 中接收到该内容。使用标签头和标签尾来闭合

    • 如果除了 children 中的 JSX ,还想要传递一些预定义的 prop的话,也可以设置自闭和传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    function List({ children, title, footer = <div>默认底部</div> }) {
    return (
    <>
    <h2>{title}</h2>
    <ul>
    {children}
    </ul>
    {footer}
    </>
    );
    }

    export default function App() {
    return (
    <>
    <List
    title="列表1"
    footer={<p>这是底部内容1</p>}
    >
    <li>内容1</li>
    <li>内容2</li>
    <li>内容3</li>
    </List>
    <List
    title="列表2"
    footer={<p>这是底部内容2</p>}
    >
    <li>内容A</li>
    <li>内容B</li>
    <li>内容C</li>
    </List>
    </>
    );
    }

子组件传递数据给父组件

这里想要在子组件中status的值发生变化后,回传status给父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { useState } from "react";

// 子组件调用父组件传递过来的函数,将发生变化的值传递到父组件中的function handleActive中
function Detail({ onActive }) {
const [status, setStatus] = useState(false);

function handleClick() {
setStatus(!status);
// 调用父组件传递过来的函数
onActive(status);
}

return (
<div>
<button onClick={handleClick}>按钮</button>
<p style={{ display: status ? 'block' : 'none' }}>Detail的内容</p>
</div>
);
}

// 父组件传递一个函数handleActive给子组件
export default function App() {
function handleActive(status) {
console.log(status);
}

return (
<>
<Detail onActive={handleActive} />
</>
);
}

同级组件之间的数据传递

参考文章