react从零开始的基础课

news2025/4/19 0:01:12

全文约5万字。

1.hello,..

// App.jsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <Greeting name="world" />
    </>
  )
}

function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>
}

export default App

2.创建一个应用的方法。

创建一个 React 应用 – React中文文档 | React中文文档镜像

 3.我的学习过程

步骤 1: 安装构建工具

npm create vite@latest my-app -- --template react

可能需要选择。我的选择是 react javascript+swc

4.vscode的官方推荐的插件

eslint  prettier

5.react开发者工具

React 开发者工具 – React中文文档 | React中文文档镜像

根据要求完成,不同浏览器可能不一样的方法。

React 组件是常规的 JavaScript 函数,但 组件的名称必须以大写字母开头,否则它们将无法运行!

 6.组件

 组件 是 React 的核心概念之一。它们是构建用户界面(UI)的基础,是你开始 React 之旅的最佳起点!

 7.定义组件

React 组件是一段可以 使用标签进行扩展 的 JavaScript 函数。如下所示(你可以编辑下面的示例):

//Greeting.jsx
import React from 'react';
function Greeting(props) {
    return <h1>Hello, {props.name}!</h1>
  }
export default Greeting;

7.1导出组件

export default 前缀是一种 JavaScript 标准语法(非 React 的特性)。它允许你导出一个文件中的主要函数以便你以后可以从其他文件引入它。

7.2 定义函数

function Greeting(props)定义名为Greeting 的 JavaScript 函数。

React 组件是常规的 JavaScript 函数,但 组件的名称必须以大写字母开头,否则它们将无法运行!

 7.3添加标签

 返回语句可以全写在一行上,如下面组件中所示:

return <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />;

但是,如果你的标签和 return 关键字不在同一行,则必须把它包裹在一对括号中,如下所示:

return (
  <div>
    <img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
  </div>
);

没有括号包裹的话,任何在 return 下一行的代码都 将被忽略https://stackoverflow.com/questions/2846283/what-are-the-rules-for-javascripts-automatic-semicolon-insertion-asi

7.4使用组件

import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'

function App() {
  const name = 'world'
  return (
    <>
      <Greeting name={name} />
    </>
  )
}

export default App

 8.组件的导入和导出

以下三个步骤对组件进行拆分:

  1. 创建 一个新的 JS 文件来存放该组件。
  2. 导出 该文件中的函数组件(可以使用 默认导出 或 具名导出)
  3. 在需要使用该组件的文件中 导入(可以根据相应的导出方式使用 默认导入 或 具名导入)。

该示例中需要注意的是,如何将组件拆分成两个文件:

  1. Gallery.js:
    • 定义了 Profile 组件,该组件仅在该文件内使用,没有被导出。
    • 使用 默认导出 的方式,将 Gallery 组件导出
  2. App.js:
    • 使用 默认导入 的方式,从 Gallery.js 中导入 Gallery 组件。
    • 使用 默认导出 的方式,将根组件 App 导出。

 通常,文件中仅包含一个组件时,人们会选择默认导出,而当文件中包含多个组件或某个值需要导出时,则会选择具名导出。 无论选择哪种方式,请记得给你的组件和相应的文件命名一个有意义的名字。我们不建议创建未命名的组件,比如 export default () => {},因为这样会使得调试变得异常困难。

// Gallery.jsx
import svg from './assets/react.svg';

function Profile() {
    return (
        <div className="profile">
        <h1>Profile</h1>
        <img src={svg} alt="SVG" />
        <p>Here is an SVG image.</p>
        </div>
    );
}
function Gallery() {
    return (
        <>  
        <Profile />
        </>
    );
    }

export default Gallery;
// import React from 'react';

import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'

function App() {
  const name = 'world'
  return (
    <>
      <Greeting name={name} />
      <Gallery />
    </>
  )
}

export default App

8.1从同一文件中导出和导入多个组件

如果你只想展示一个 Profile 组,而不展示整个图集。你也可以导出 Profile 组件。但 Gallery.js 中已包含 默认 导出,此时,你不能定义 两个 默认导出。但你可以将其在新文件中进行默认导出,或者将 Profile 进行 具名 导出。同一文件中,有且仅有一个默认导出,但可以有多个具名导出!

为了减少在默认导出和具名导出之间的混淆,一些团队会选择只使用一种风格(默认或者具名),或者禁止在单个文件内混合使用。这因人而异,选择最适合你的即可! 

首先,用具名导出的方式,将 Profile 组件从 Gallery.js 导出(不使用 default 关键字):

 
 

export function Profile() {

// ...

}

接着,用具名导入的方式,从 Gallery.js 文件中 导入 Profile 组件(用大括号):

 
 

import { Profile } from './Gallery.js';

好像现在不加{}也可以直接使用了。

最后,在 App 组件里 渲染 <Profile />

 
 

export default function App() {

return <Profile />;

}

 现在,Gallery.js 包含两个导出:一个是默认导出的 Gallery,另一个是具名导出的 ProfileApp.js 中均导入了这两个组件。尝试将 <Profile /> 改成 <Gallery />,回到示例中:

8.2相关代码

// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'

function App() {
  const name = 'world'
  return (
    <>
      <Greeting name={name} />
      <Gallery />
      <Profile />
    </>
  )
}

export default App

// Gallery.jsx
import svg from './assets/react.svg';

export function Profile() {
    return (
        <div className="profile">
        <h1>Profile</h1>
        <img src={svg} alt="SVG" />
        <p>Here is an SVG image.</p>
        </div>
    );
}
function Gallery() {
    return (
        <>  
        <Profile />
        </>
    );
    }

export default Gallery;
// import React from 'react';

 

在本章节中,你学到了:

  • 何为根组件
  • 如何导入和导出一个组件
  • 何时和如何使用默认和具名导入导出
  • 如何在一个文件里导出多个组件

 9.使用 JSX 书写标签语言

9.1为什么 React 将标签和渲染逻辑耦合在一起

随着 Web 的交互性越来越强,逻辑越来越决定页面中的内容。JavaScript 控制着 HTML 的内容!这也是为什么 在 React 中,渲染逻辑和标签共同存在于同一个地方——组件。

 

将一个按钮的渲染逻辑和标签放在一起可以确保它们在每次编辑时都能保持互相同步。反之,彼此无关的细节是互相隔离的,例如按钮的标签和侧边栏的标签。这样我们在修改其中任意一个组件时会更安全。

每个 React 组件都是一个 JavaScript 函数,它会返回一些标签,React 会将这些标签渲染到浏览器上。React 组件使用一种被称为 JSX 的语法扩展来描述这些标签。JSX 看起来和 HTML 很像,但它的语法更加严格并且可以动态展示信息。了解这些区别最好的方式就是将一些 HTML 标签转化为 JSX 标签。

注意

JSX and React 是相互独立的 东西。但它们经常一起使用,但你 可以 单独使用它们中的任意一个,JSX 是一种语法扩展,而 React 则是一个 JavaScript 的库。

 9.2将HTML代码转成JSX

如有以下代码

<h1>海蒂·拉玛的待办事项</h1>
<img 
  src="https://i.imgur.com/yXOvdOSs.jpg" 
  alt="Hedy Lamarr" 
  class="photo"
>
<ul>
    <li>发明一种新式交通信号灯
    <li>排练一个电影场景
    <li>改进频谱技术
</ul>

9.3JSX规则

9.3.1只能返回一个根元素

JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

9.3.2标签必须闭合

<>
  <img 
    src="https://i.imgur.com/yXOvdOSs.jpg" 
    alt="Hedy Lamarr" 
    class="photo"
   />
  <ul>
      <li>发明一种新式交通信号灯</li>
      <li>排练一个电影场景</li>
      <li>改进频谱技术</li>
  </ul>
</>

这个空标签被称作 Fragment。React Fragment 允许你将子元素分组,而不会在 HTML 结构中添加额外节点。

9.3.3 使用驼峰式命名法给 所有 大部分属性命名!

JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。在你自己的组件中,经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制。例如,变量名称不能包含 - 符号或者像 class 这样的保留字。
这就是为什么在 React 中,大部分 HTML 和 SVG 属性都用驼峰式命名法表示。例如,需要用 strokeWidth 代替 stroke-width。由于 class 是一个保留字,所以在 React 中需要用 className 来代替。这也是 DOM 属性中的命名:
<img 
  src="https://i.imgur.com/yXOvdOSs.jpg" 
  alt="Hedy Lamarr" 
  className="photo"
/>
你可以 在 React DOM 元素中找到所有对应的属性。如果你在编写属性时发生了错误,不用担心 —— React 会在 浏览器控制台 中打印一条可能的更正信息。

10. 在 JSX 中通过大括号使用 JavaScript

你将会学习到

  • 如何使用引号传递字符串
  • 在 JSX 的大括号内引用 JavaScript 变量
  • 在 JSX 的大括号内调用 JavaScript 函数
  • 在 JSX 的大括号内使用 JavaScript 对象

10.1使用单引号或双引号把一个字符串传递给JSX

export function Profile() {
    return (
        <div className="profile">
        <h1>Profile</h1>
        <img src={svg} alt='SVG' />
        <p>Here is an SVG image.</p>
        </div>
    );
}

 但是如果你想要动态地指定 srcalt 的值呢?你可以 {} 替代 "" 以使用 JavaScript 变量

// Gallery.jsx
import svg from './assets/react.svg';

export function Profile() {
    const title= 'Profile';
    const description = 'Here is an SVG image.';
    const image = svg;
    return (
        <div className="profile">
        <h1>{title}</h1>
        <img src={image} alt='SVG' />
        <p>{description}</p>
        </div>
    );
}
function Gallery() {
    return (
        <>  
        <Profile />
        </>
    );
    }

export default Gallery;
// import React from 'react';

10.2可以在哪使用大括号

在 JSX 中,只能在以下两种场景中使用大括号:

  1. 用作 JSX 标签内的文本<h1>{name}'s To Do List</h1> 是有效的,但是 <{tag}>Gregorio Y. Zara's To Do List</{tag}> 无效。
  2. 用作紧跟在 = 符号后的 属性src={avatar} 会读取 avatar 变量,但是 src="{avatar}" 只会传一个字符串 {avatar}

10.3使用 “双大括号”:JSX 中的 CSS 和 对象 

除了字符串、数字和其它 JavaScript 表达式,你甚至可以在 JSX 中传递对象。对象也用大括号表示,例如 { name: "Hedy Lamarr", inventions: 5 }。因此,为了能在 JSX 中传递,你必须用另一对额外的大括号包裹对象:person={{ name: "Hedy Lamarr", inventions: 5 }}

你可能在 JSX 的内联 CSS 样式中就已经见过这种写法了。React 不要求你使用内联样式(使用 CSS 类就能满足大部分情况)。但是当你需要内联样式的时候,你可以给 style 属性传递一个对象:

// TodoList.jsx
function TodoList() {
    return (
      <ul style={{
        backgroundColor: 'black',
        color: 'pink'
      }}>
        <li>Improve the videophone</li>
        <li>Prepare aeronautics lectures</li>
        <li>Work on the alcohol-fuelled engine</li>
      </ul>
    );
  }
  export default TodoList;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'

function App() {
  const name = 'world'
  return (
    <>
      <Greeting name={name} />
      <Gallery />
      <Profile />
      <TodoList />
    </>
  )
}

export default App

<ul style="background-color: black; color: pink;"><li>Improve the videophone</li><li>Prepare aeronautics lectures</li><li>Work on the alcohol-fuelled engine</li></ul> 

内联 style 属性 使用驼峰命名法编写。例如,HTML <ul style="background-color: black"> 在你的组件里应该写成 <ul style={{ backgroundColor: 'black' }}>。 

 本节优化后的代码如下
 

// TodoListMore.jsx
const pserson={
    name: '张老三',
    age: 30,
    now: new Date(),
    hobbies: ['reading', 'gaming', 'hiking'],
    theme: {
        backgroundColor: 'black',
        color: 'pink'
    }
    
}
const today = pserson.now.toLocaleDateString('en-US', {
    month: 'long',
    day: 'numeric',
    year: 'numeric'
});

function TodoListMore() {
    return (
      <ul style={pserson.theme}>
        <h1>{pserson.name}日程表:{today}</h1>
        <h2>他今年有{pserson.age}岁了</h2>
        <ul>
            {pserson.hobbies.map((hobby, index) => (
                <li key={index}>{hobby}</li>
            ))}
        </ul>
      </ul>
    );
  }
    export default TodoListMore;
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'

function App() {
  // const name = 'world'
  return (
    <>
<TodoListMore />
    </>
  )
}

export default App

 JSX 中,`style` 和 `className` 是两种不同的样式控制方式-CSDN博客https://blog.csdn.net/weixin_42771529/article/details/147094039?sharetype=blogdetail&sharerId=147094039&sharerefer=PC&sharesource=weixin_42771529&spm=1011.2480.3001.8118

JSX 是一种模板语言的最小实现,因为它允许你通过 JavaScript 来组织数据和逻辑。

摘要

现在你几乎了解了有关 JSX 的一切:

  • JSX 引号内的值会作为字符串传递给属性。
  • 大括号让你可以将 JavaScript 的逻辑和变量带入到标签中。
  • 它们会在 JSX 标签中的内容区域或紧随属性的 = 后起作用。
  • {{ 和 }} 并不是什么特殊的语法:它只是包在 JSX 大括号内的 JavaScript 对象。

11. 将 Props 传递给组件

将会学习到

  • 如何向组件传递 props
  • 如何从组件读取 props
  • 如何为 props 指定默认值
  • 如何给组件传递 JSX
  • Props 如何随时间变化

 Props 是你传递给 JSX 标签的信息。例如,classNamesrcaltwidthheight 便是一些可以传递给 <img> 的 props:

11.1普通传参的示例

 相关代码如下:

// Avatar.jsx
function Avatar({person, size=100}) {
  return (
    <div>
      <img
        src="https://www.w3schools.com/w3css/img_avatar3.png"
        // alt={`${person.name}'s avatar`}
        style={{
          width: size,
          height: size,
          borderRadius: "50%",
          border: "2px solid #000",
        }}
      />
      <p>{`${person.name}'s avatar`}</p>
      <hr />
        <p>Size: {size}px</p>
    </div>
  );
}
export default Avatar;

注意的是size不要用{}。用了无效果。和站点上介绍的不一样。可能改了。 后面的要用。

要注意size可以给一个默认值。

// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'

function App() {
  // const name = 'world'
  return (
    <>
<TodoListMore />
<Avatar person={{name: 'John Doe'}} size={80} />
    </>
  )
}

export default App

传入的参数要注意一下写法,特别是 

如果 person= 后面的双花括号让你感到困惑,请记住,在 JSX 花括号中,它们只是一个对象。

注意

在声明 props 时, 不要忘记 () 之间的一对花括号 {}

 
 

function Avatar({ person, size }) {

// ...

}

这种语法被称为 “解构”,等价于于从函数参数中读取属性:

 
 

function Avatar(props) {

let person = props.person;

let size = props.size;

// ...

}

11.2 将 JSX 作为子组件传递 

有时你会希望以相同的方式嵌套自己的组件:

<Card>

<Avatar />

</Card>

 本节学习示例

// GetImageUrl.jsx
export function GetImageUrl(size=1) {
    return (
        // https://www.w3schools.com/w3css/img_avatar3.png
      'https://www.w3schools.com/w3css/img_avatar' +
      size +
      '.png'
    );
  }

// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
function Avatar({person, size=100,num=1}) {
  return (
    <div>
      <img
        src={GetImageUrl(num)}
        // alt={`${person.name}'s avatar`}
        style={{
          width: size,
          height: size,
          borderRadius: "50%",
          border: "2px solid #000",
        }}
      />
      <p>{`${person.name}'s avatar`}</p>
      <hr />
        <p>Size: {size}px</p>
        <p>Num: {num}</p>
        <p>Image URL: {GetImageUrl(num)}</p>
    </div>
  );
}
export default Avatar;

// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'

function App() {
  // const name = 'world'
  return (
    <>
{/* <TodoListMore /> */}
<Avatar person={{name: '张三'}} size={80} num={1}/>
<Avatar person={{name: '李四'}} size={80} num={2}/>
<Avatar person={{name: '王五'}} size={80} num={3}/>
    </>
  )
}

export default App

添加一个时钟,验证props的性质 。站上的信息与代码不全。以下是完全的

 为了让网页上的时钟自动更新,你需要借助 React 的 useState 和 useEffect 钩子。useState 用来管理时钟的时间状态,而 useEffect 则负责在组件挂载时启动一个定时器,定时更新时间状态。

代码解释:

  1. 导入钩子:引入 useState 和 useEffect 钩子。
  2. 状态管理:借助 useState 钩子创建一个名为 time 的状态变量,初始值为当前日期和时间。
  3. 副作用处理:利用 useEffect 钩子在组件挂载时设置一个定时器,每秒更新一次 time 状态。
  4. 清除定时器:在 useEffect 的返回函数里清除定时器,避免出现内存泄漏问题。
  5. 渲染时间:在组件的返回值中使用 time.toLocaleTimeString() 渲染当前时间。
 

经过这些修改,时钟就会每秒自动更新一次。

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

function Clock({ color }) {
    const [time, setTime] = useState(new Date());

    useEffect(() => {
        const intervalId = setInterval(() => {
            setTime(new Date());
        }, 1000);

        return () => {
            clearInterval(intervalId);
        };
    }, []);

    return (
        <div>
            <h1>Hello, world!</h1>
            <h2 style={{ color: color }}>It is {time.toLocaleTimeString()}.</h2>
        </div>
    );
}

export default Clock;
    
// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'
import Clock from './Clock.jsx'

function App() {
  // const name = 'world'
  return (
    <>
{/* <TodoListMore /> */}
{/* <Avatar person={{name: '张三'}} size={80} num={1}/>
<Avatar person={{name: '李四'}} size={80} num={2}/>
<Avatar person={{name: '王五'}} size={80} num={3}/> */}
<Clock color="red"/>
<Clock color="blue"/>
<Clock color="green"/>
    </>
  )
}

export default App

可以实现自动更新了。

 

明天再接着学习。

12.条件渲染

通常你的组件会需要根据不同的情况显示不同的内容。在 React 中,你可以通过使用 JavaScript 的 if 语句、&&? : 运算符来选择性地渲染 JSX。

 12.1条件返回jsx

结合以上代码进行解释。

对人物的性别进行判断

// IsMan.jsx
function IsMan(person) {
    if (person.gender === 'male') {
        return <li className="item">男✅</li>;
    } else if (person.gender === 'female') {
        return <li className="item">女</li>;
    }
    return '未知';
}

export default IsMan;    

在Avatar.jsx中使用。注意传进去的是数组对象

// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";

function Avatar({ person, size = 100, num = 1 }) {
  const gender = IsMan(person);
  return (
    <div>
      <img
        src={GetImageUrl(num)}
        style={{
          width: size,
          height: size,
          borderRadius: "50%",
          border: "2px solid #000",
        }}
      />
      <p>{person.name} is {gender}`</p>
      <hr />
      <p>Size: {size}px</p>
      <p>Num: {num}</p>
      <p>Image URL: {GetImageUrl(num)}</p>
    </div>
  );
}

export default Avatar;    

最后在App.jsx中使用。注意性别的写法。一定要在person对象中。

// App.jsx
import React from 'react'
import './App.css'
import Greeting from './Greeting.jsx'
import Gallery from './Gallery.jsx'
import Profile from './Gallery.jsx'
import TodoList from './TodoList.jsx'
import TodoListMore from './assets/TodoListMore.jsx'
import Avatar from './Avatar.jsx'
import Clock from './Clock.jsx'

function App() {
  return (
    <>
      <Avatar person={{ name: '张三', gender: 'male' }} size={80} num={1} />
      <Avatar person={{ name: '李四', gender: 'female' }} size={80} num={2} />
      <Avatar person={{ name: '王五', gender: 'male' }} size={80} num={3} />
      <Clock color="red" />
    </>
  )
}

export default App    

 

我感觉也可以直接返回所有需要的值。下面是修改的部分

// IsMan.jsx
function IsMan(person) {
    if (person.gender === 'male') {
        return <li className="item">{person.name}:男✅</li>;
    } else if (person.gender === 'female') {
        return <li className="item">{person.name}:女</li>;
    }
    return '未知';
}

export default IsMan;    
// Avatar.jsx
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";

function Avatar({ person, size = 100, num = 1 }) {
  const name_gender = IsMan(person);
  return (
    <div>
      <img
        src={GetImageUrl(num)}
        style={{
          width: size,
          height: size,
          borderRadius: "50%",
          border: "2px solid #000",
        }}
      />
      <p>{name_gender}`</p>
      <hr />
      <p>Size: {size}px</p>
      <p>Num: {num}</p>
      <p>Image URL: {GetImageUrl(num)}</p>
    </div>
  );
}

export default Avatar;    

是不是可以优化呢? 我认为可以如下:

function IsMan(person) {
    return person.gender === 'male' ? <li className="item">{person.name}:男✅</li> : <li className="item">{person.name}:女</li>;
}

export default IsMan;    

本节学习的内容

  • 在 React,你可以使用 JavaScript 来控制分支逻辑。
  • 你可以使用 if 语句来选择性地返回 JSX 表达式。
  • 你可以选择性地将一些 JSX 赋值给变量,然后用大括号将其嵌入到其他 JSX 中。
  • 在 JSX 中,{cond ? <A /> : <B />} 表示 “当 cond 为真值时, 渲染 <A />,否则 <B />
  • 在 JSX 中,{cond && <A />} 表示 “当 cond 为真值时, 渲染 <A />,否则不进行渲染”
  • 快捷的表达式很常见,但如果你更倾向于使用 if,你也可以不使用它们。

下面将本节所学的内容做成如下的效果

 

相关代码如下:

// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';

function App() {
    const people = [
        { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
        { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
        { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
        { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    ];

    return (
        <div className="container mx-auto p-4">
            <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
                {people.map((person, index) => (
                    <Avatar
                        key={index}
                        person={person}
                        size={80}
                        num={person.num}
                    />
                ))}
            </div>
        </div>
    );
}

export default App;    
// // Avatar.jsx
// import { GetImageUrl } from "./GetImageUrl";
// import IsMan from "./IsMan";

// function Avatar({ person, size = 100, num = 1 }) {
//   const name_gender = IsMan(person);
//   return (
//     <div>
//       <img
//         src={GetImageUrl(num)}
//         style={{
//           width: size,
//           height: size,
//           borderRadius: "50%",
//           border: "2px solid #000",
//         // }}
//       />
//       <p>{name_gender}`</p>
//       <hr />
//       {/* <p>Size: {size}px</p> */}
//       {/* <p>Num: {num}</p> */}
//       {/* <p>Image URL: {GetImageUrl(num)}</p> */}
//     </div>
//   );
// }

// export default Avatar;    
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";

function Avatar({ person, size = 100, num = 1 }) {
    const gender = IsMan(person);
    return (
        <div className="flex flex-col items-center p-4 border border-gray-300 rounded-md shadow-md m-4">
            <img
                src={GetImageUrl(num)}
                alt={`${person.name}'s avatar`}
                style={{
                  width: size,
                  height: size,
                  borderRadius: "50%",
                  border: "2px solid #000",
                }}
            />
            {/* <p className="text-lg font-bold mt-2">{person.name}</p> */}
            <p className="text-gray-600">{gender}</p>
            <li className="text-gray-600">学历: {person.education}</li>
            <li className="text-gray-600">婚否: {person.married ? '已婚' : '未婚'}</li>
            <li className="text-gray-600">擅长: {person.skills}</li>
        </div>
    );
}

export default Avatar;    
// // Avatar.jsx
// import { GetImageUrl } from "./GetImageUrl";
// import IsMan from "./IsMan";

// function Avatar({ person, size = 100, num = 1 }) {
//   const name_gender = IsMan(person);
//   return (
//     <div>
//       <img
//         src={GetImageUrl(num)}
//         style={{
//           width: size,
//           height: size,
//           borderRadius: "50%",
//           border: "2px solid #000",
//         // }}
//       />
//       <p>{name_gender}`</p>
//       <hr />
//       {/* <p>Size: {size}px</p> */}
//       {/* <p>Num: {num}</p> */}
//       {/* <p>Image URL: {GetImageUrl(num)}</p> */}
//     </div>
//   );
// }

// export default Avatar;    
import { GetImageUrl } from "./GetImageUrl";
import IsMan from "./IsMan";

function Avatar({ person, size = 100, num = 1 }) {
    const gender = IsMan(person);
    return (
        <div className="flex flex-col items-center p-4 border border-gray-300 rounded-md shadow-md m-4">
            <img
                src={GetImageUrl(num)}
                alt={`${person.name}'s avatar`}
                style={{
                  width: size,
                  height: size,
                  borderRadius: "50%",
                  border: "2px solid #000",
                }}
            />
            {/* <p className="text-lg font-bold mt-2">{person.name}</p> */}
            <p className="text-gray-600">{gender}</p>
            <li className="text-gray-600">学历: {person.education}</li>
            <li className="text-gray-600">婚否: {person.married ? '已婚' : '未婚'}</li>
            <li className="text-gray-600">擅长: {person.skills}</li>
        </div>
    );
}

export default Avatar;    

// GetImageUrl.jsx
export function GetImageUrl(size=1) {
    return (
        // https://www.w3schools.com/w3css/img_avatar3.png
      'https://www.w3schools.com/w3css/img_avatar' +
      size +
      '.png'
    );
  }

以上内容将在下一节及后序渲染列表中进一步讲解。

13,渲染列表Render the list.

13.1学习内容:

你可能经常需要通过 JavaScript 的数组方法 来操作数组中的数据,从而将一个数据集渲染成多个相似的组件。在这篇文章中,你将学会如何在 React 中使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。

你将会学习到

  • 如何通过 JavaScript 的 map() 方法从数组中生成组件
  • 如何通过 JavaScript 的 filter() 筛选需要渲染的组件
  • 何时以及为何使用 React 中的 key

 13.2从数组中渲染数据

13.2.1,首先把数据存储在数组中。或从数据库中得到。

import { GetImageUrl } from "./GetImageUrl"

// RendreList.jsx


const people = [
  { id:1,name: 'Alice', age: 25, isMan: true },
  { id:2,name: 'Bob', age: 30, isMan: true },
  { id:3,name: 'Charlie', age: 28, isMan: true },
  { id:4,name: 'David', age: 35, isMan: false },
  { id:5,name: 'Eve', age: 27, isMan: false },
]

const listItems = people.map((person) => (
  <li className="item" key={person.name}>
    <img src={GetImageUrl(person.id)} alt={person.name} style={{
                  width: 40,
                  height: 40,
                  borderRadius: "50%",
                  border: "2px solid #000",
                }}/>
    {person.name}:{person.age}
    {person.isMan ? '男✅' : '女'}
  </li>
))

function RenderList() {
  return (
    <div>
      <ul>{listItems}</ul>
    </div>
  )
}
export default RenderList

以上代码中拟定了一个数组。然后把数据使用map转成独立的person,然后取各个person的值,中间用一个三目方式取出了性别。注意层级关系。

// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';

function App() {
    // const people = [
    //     { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
    //     { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
    //     { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
    //     { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    // ];

    return (
        // <div className="container mx-auto p-4">
        //     <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
        //     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        //         {people.map((person, index) => (
        //             <Avatar
        //                 key={index}
        //                 person={person}
        //                 size={80}
        //                 num={person.num}
        //             />
        //         ))}
        //     </div>
        // </div>
        <RenderList />

    );
}

export default App;    
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';

function App() {
    // const people = [
    //     { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
    //     { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
    //     { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
    //     { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    // ];

    return (
        // <div className="container mx-auto p-4">
        //     <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
        //     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        //         {people.map((person, index) => (
        //             <Avatar
        //                 key={index}
        //                 person={person}
        //                 size={80}
        //                 num={person.num}
        //             />
        //         ))}
        //     </div>
        // </div>
        <RenderList />

    );
}

export default App;    

注意我把多余的内容给注释了,

效果图:

陷阱

因为箭头函数会隐式地返回位于 => 之后的表达式,所以你可以省略 return 语句。

 
 

const listItems = chemists.map(person =>

<li>...</li> // 隐式地返回!

);

不过,如果你的 => 后面跟了一对花括号 { ,那你必须使用 return 来指定返回值!

 
 

const listItems = chemists.map(person => { // 花括号

return <li>...</li>;

});

箭头函数 => { 后面的部分被称为 “块函数体”,块函数体支持多行代码的写法,但要用 return 语句才能指定返回值。假如你忘了写 return,那这个函数什么都不会返回!

 key的作用

用 key 保持列表项的顺序 

如果把上面任何一个沙盒示例在新标签页打开,你就会发现控制台有这样一个报错:

Console

Warning: Each child in a list should have a unique “key” prop.

这是因为你必须给数组中的每一项都指定一个 key——它可以是字符串或数字的形式,只要能唯一标识出各个数组项就行:

 
 

<li key={person.id}>...</li>

注意

直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值!

这些 key 会告诉 React,每个组件对应着数组里的哪一项,所以 React 可以把它们匹配起来。这在数组项进行移动(例如排序)、插入或删除等操作时非常重要。一个合适的 key 可以帮助 React 推断发生了什么,从而得以正确地更新 DOM 树。

用作 key 的值应该在数据中提前就准备好,而不是在运行时才随手生成:

更多知识

如何设定 key 值 

不同来源的数据往往对应不同的 key 值获取方式:

  • 来自数据库的数据: 如果你的数据是从数据库中获取的,那你可以直接使用数据表中的主键,因为它们天然具有唯一性。
  • 本地产生数据: 如果你数据的产生和保存都在本地(例如笔记软件里的笔记),那么你可以使用一个自增计数器或者一个类似 uuid 的库来生成 key。

key 需要满足的条件 

  • key 值在兄弟节点之间必须是唯一的。 不过不要求全局唯一,在不同的数组中可以使用相同的 key。
  • key 值不能改变,否则就失去了使用 key 的意义!所以千万不要在渲染时动态地生成 key。

React 中为什么需要 key? 

设想一下,假如你桌面上的文件都没有文件名,取而代之的是,你需要通过文件的位置顺序来区分它们———第一个文件,第二个文件,以此类推。也许你也不是不能接受这种方式,可是一旦你删除了其中的一个文件,这种组织方式就会变得混乱无比。原来的第二个文件可能会变成第一个文件,第三个文件会成为第二个文件……

React 里需要 key 和文件夹里的文件需要有文件名的道理是类似的。它们(key 和文件名)都让我们可以从众多的兄弟元素中唯一标识出某一项(JSX 节点或文件)。而一个精心选择的 key 值所能提供的信息远远不止于这个元素在数组中的位置。即使元素的位置在渲染的过程中发生了改变,它提供的 key 值也能让 React 在整个生命周期中一直认得它。

陷阱

你可能会想直接把数组项的索引当作 key 值来用,实际上,如果你没有显式地指定 key 值,React 确实默认会这么做。但是数组项的顺序在插入、删除或者重新排序等操作中会发生改变,此时把索引顺序用作 key 值会产生一些微妙且令人困惑的 bug。

与之类似,请不要在运行过程中动态地产生 key,像是 key={Math.random()} 这种方式。这会导致每次重新渲染后的 key 值都不一样,从而使得所有的组件和 DOM 元素每次都要重新创建。这不仅会造成运行变慢的问题,更有可能导致用户输入的丢失。所以,使用能从给定数据中稳定取得的值才是明智的选择。

有一点需要注意,组件不会把 key 当作 props 的一部分。Key 的存在只对 React 本身起到提示作用。如果你的组件需要一个 ID,那么请把它作为一个单独的 prop 传给组件: <Profile key={id} userId={id} />

 13.3过滤

在 RenderList 组件上方添加按性别筛选显示的功能。你可以通过选择 “全部”“男”“女” 来筛选列表中的人员。

  1. 引入 useState:借助 useState 钩子来管理筛选状态,初始值设为 'all'
  2. 筛选人员列表:依据当前的筛选状态对 people 数组进行过滤,生成 filteredPeople 数组。
  3. 添加筛选下拉框:在组件上方添加一个下拉框,提供 “全部”“男”“女” 三个选项,当选择发生变化时,更新筛选状态。
  4. 渲染筛选后的列表:对 filteredPeople 数组进行映射,渲染出筛选后的人员列表。

 修改后的代码

import { GetImageUrl } from "./GetImageUrl"
import React,{useState} from "react"

// RendreList.jsx


const people = [
  { id:1,name: 'Alice', age: 25, isMan: true },
  { id:2,name: 'Bob', age: 30, isMan: true },
  { id:3,name: 'Charlie', age: 28, isMan: true },
  { id:4,name: 'David', age: 35, isMan: false },
  { id:5,name: 'Eve', age: 27, isMan: false },
]

// const listItems = people.map((person) => (
//   <li className="item" key={person.id}>
//     <img src={GetImageUrl(person.id)} alt={person.name} style={{
//                   width: 40,
//                   height: 40,
//                   borderRadius: "50%",
//                   border: "2px solid #000",
//                 }}/>
//     {person.name}:{person.age}
//     {person.isMan ? '男✅' : '女'}
//     <hr />
//   </li>
// ))

function RenderList() {
    const [filteredPeople, setFilteredPeople] = useState("all");
    const filteredPeopleList = people.filter((person) => {
      if (filteredPeople === "all") {
        return true;
      } else if (filteredPeople === "man") {
        return person.isMan;
      } else if (filteredPeople === "woman") {
        return !person.isMan;
      }
    });
    const listItems = filteredPeopleList.map((person) => (
      <li className="item" key={person.id}>
        <img src={GetImageUrl(person.id)} alt={person.name} style={{
                      width: 40,
                      height: 40,
                      borderRadius: "50%",
                      border: "2px solid #000",
                    }}/>
        {person.name}:{person.age}
        {person.isMan ? '男✅' : '女'}
        <hr />
      </li>
    ))
  return (
    <>  
    <div>
      <select name="" id=""
        value={filteredPeople} onChange={(e) => setFilteredPeople(e.target.value)}>
            <option value="all">全部</option>
            <option value="man">男</option>
            <option value="woman">女</option>
      </select>
    </div>
    <ul className="list">{listItems}</ul>

    </>
  )
}
export default RenderList

将 const listItems = filteredPeopleList.map((person) => {... }) 放在函数内部有几个合理的原因,主要涉及到数据的动态更新和作用域相关的问题:

 
  1. 动态更新:在你的 RenderList 组件中,使用了 useState 钩子来管理 filteredPeople 状态。当用户通过 select 元素选择不同的筛选选项时,filteredPeople 的值会发生变化,进而导致 filteredPeopleList 数组的内容也发生变化(因为 filteredPeopleList 是根据 filteredPeople 的值对 people 数组进行过滤得到的)。
 

如果 listItems 的计算是在函数外部,那么它只会在组件第一次渲染时被计算一次,后续 filteredPeople 的变化不会影响到 listItems 的值,也就无法实现动态的筛选效果。而将 listItems 的计算放在函数内部,每次组件因为状态更新而重新渲染时,listItems 都会根据最新的 filteredPeopleList 重新计算,从而确保显示的是筛选后正确的列表内容。

 
  1. 作用域问题filteredPeopleList 是在 RenderList 函数内部通过对 people 数组的过滤操作得到的变量。如果 listItems 的计算在函数外部,那么它无法访问到 filteredPeopleList 变量(因为 filteredPeopleList 的作用域仅限于 RenderList 函数内部),会导致代码报错。将 listItems 的计算放在函数内部,它就可以在正确的作用域内访问到 filteredPeopleList 并进行映射操作。
 

综上所述,将 listItems 的计算放在 RenderList 函数内部是为了确保筛选功能的正确实现,能够根据状态的变化动态更新显示的列表内容。

 效果如下:

14,保持组件纯粹

部分 JavaScript 函数是 纯粹 的,这类函数通常被称为纯函数。纯函数仅执行计算操作,不做其他操作。你可以通过将组件按纯函数严格编写,以避免一些随着代码库的增长而出现的、令人困扰的 bug 以及不可预测的行为。但为了获得这些好处,你需要遵循一些规则。

你将会学习到

  • 纯函数是什么,以及它如何帮助你避免 bug
  • 如何将数据变更与渲染过程分离,以保持组件的纯粹
  • 如何使用严格模式发现组件中的错误

14.1 纯函数:组件作为公式

在计算机科学中(尤其是函数式编程的世界中),纯函数 通常具有如下特征:

  • 只负责自己的任务。它不会更改在该函数调用前就已存在的对象或变量。
  • 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果。

举个你非常熟悉的纯函数示例:数学中的公式。

考虑如下数学公式:y = 2x。

若 x = 2 则 y = 4。永远如此。

若 x = 3 则 y = 6。永远如此。

若 x = 3,那么 y 并不会因为时间或股市的影响,而有时等于 9 、 –1 或 2.5。

若 y = 2x 且 x = 3, 那么 y 永远 等于 6.

我们使用 JavaScript 的函数实现,看起来将会是这样:

 
 

function double(number) {

return 2 * number;

}

上述例子中,double() 就是一个 纯函数。如果你传入 3 ,它将总是返回 6

React 便围绕着这个概念进行设计。React 假设你编写的所有组件都是纯函数。也就是说,对于相同的输入,你所编写的 React 组件必须总是返回相同的 JSX。

14.2副作用:不符合预期的后果

 React 的渲染过程必须自始至终是纯粹的。组件应该只 返回 它们的 JSX,而改变 在渲染前,就已存在的任何对象或变量 — 这将会使它们变得不纯粹!

以下为违反规则的组件

// CupNo.jsx
let guest = 0;

function Cup() {
  // Bad:正在更改预先存在的变量!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';

function App() {
    // const people = [
    //     { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
    //     { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
    //     { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
    //     { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    // ];

    return (
        // <div className="container mx-auto p-4">
        //     <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
        //     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        //         {people.map((person, index) => (
        //             <Avatar
        //                 key={index}
        //                 person={person}
        //                 size={80}
        //                 num={person.num}
        //             />
        //         ))}
        //     </div>
        // </div>
        <>
        <RenderList />
        <TeaSet />
        </>
        

    );
}

export default App;    

很明显,已经失去了预期。

该组件正在读写其外部声明的 guest 变量。这意味着 多次调用这个组件会产生不同的 JSX!并且,如果 其他 组件读取 guest ,它们也会产生不同的 JSX,其结果取决于它们何时被渲染!这是无法预测的。

我们可以修改一下,如下

// CupNo.jsx
// let guest = 0;

function Cup({guest}) {
  // Bad:正在更改预先存在的变量!
//   guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup  guest={3} />
    </>
  );
}

现在你的组件就是纯粹的,因为它返回的 JSX 只依赖于 guest prop。

一般来说,你不应该期望你的组件以任何特定的顺序被渲染。调用 y = 5x 和 y = 2x 的先后顺序并不重要:这两个公式相互独立。同样地,每个组件也应该“独立思考”,而不是在渲染过程中试图与其他组件协调,或者依赖于其他组件。渲染过程就像是一场学校考试:每个组件都应该自己计算 JSX!

使用严格模式检测不纯的计算 
收起

尽管你可能还没使用过,但在 React 中,你可以在渲染时读取三种输入:props,state 和 context。你应该始终将这些输入视为只读。

当你想根据用户输入 更改 某些内容时,你应该 设置状态,而不是直接写入变量。当你的组件正在渲染时,你永远不应该改变预先存在的变量或对象。

React 提供了 “严格模式”,在严格模式下开发时,它将会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件

我们注意到,原始示例显示的是 “Guest #2”、“Guest #4” 和 “Guest #6”,而不是 “Guest #1”、“Guest #2” 和 “Guest #3”。原来的函数并不纯粹,因此调用它两次就出现了问题。但对于修复后的纯函数版本,即使调用该函数两次也能得到正确结果。纯函数仅仅执行计算,因此调用它们两次不会改变任何东西 — 就像两次调用 double(2) 并不会改变返回值,两次求解 y = 2x 不会改变 y 的值一样。相同的输入,总是返回相同的输出。

严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 <React.StrictMode> 包裹根组件。一些框架会默认这样做。

要注意参数引用 时用{} 

 

14.3局部 mutation:组件的小秘密

上一小节的示例的问题出在渲染过程中,组件改变了 预先存在的 变量的值。为了让它听起来更可怕一点,我们将这种现象称为 突变(mutation) 。纯函数不会改变函数作用域外的变量、或在函数调用前创建的对象——这会使函数变得不纯粹!

但是,你完全可以在渲染时更改你 刚刚 创建的变量和对象。在本示例中,你创建一个 [] 数组,将其分配给一个 cups 变量,然后 push 一打 cup 进去:

 我将使用非默认导出的方式用来对比。

// CupNo.jsx
// let guest = 0;

function Cup({guest}) {
  // Bad:正在更改预先存在的变量!
//   guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup  guest={3} />
    </>
  );
}
export function TeaGathering() {
    let cups=[]
    for (let i=8; i<13; i++) {
        cups.push(<Cup guest={i} />)
    }
//   return      cups
    return (
        <>
        <h2>Tea Gathering</h2>
        {cups}
        </>
    )

  ;
}

要注意的是在上一段代码中,如果返回时,使用单行时,要纯粹的写在一行。如何是多行时,要用()。下面是如何引用

// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';

function App() {
    // const people = [
    //     { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
    //     { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
    //     { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
    //     { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    // ];

    return (
        // <div className="container mx-auto p-4">
        //     <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
        //     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        //         {people.map((person, index) => (
        //             <Avatar
        //                 key={index}
        //                 person={person}
        //                 size={80}
        //                 num={person.num}
        //             />
        //         ))}
        //     </div>
        // </div>
        <>
        <RenderList />
        <TeaSet />
        <TeaGathering />
        </>
        

    );
}

export default App;    

以上代码要注意的是引用时,要用{}来使用。

如果 cups 变量或 [] 数组是在 TeaGathering 函数之外创建的,这将是一个很大的问题!因为如果那样的话,当你调用数组的 push 方法时,就会更改 预先存在的 对象。

但是,这里不会有影响,因为每次渲染时,你都是在 TeaGathering 函数内部创建的它们。TeaGathering 之外的代码并不会知道发生了什么。这就被称为 “局部 mutation” — 如同藏在组件里的小秘密。

React 为何侧重于纯函数? 
收起

编写纯函数需要遵循一些习惯和规程。但它开启了绝妙的机遇:

  • 你的组件可以在不同的环境下运行 — 例如,在服务器上!由于它们针对相同的输入,总是返回相同的结果,因此一个组件可以满足多个用户请求。
  • 你可以为那些输入未更改的组件来 跳过渲染,以提高性能。这是安全的做法,因为纯函数总是返回相同的结果,所以可以安全地缓存它们。
  • 如果在渲染深层组件树的过程中,某些数据发生了变化,React 可以重新开始渲染,而不会浪费时间完成过时的渲染。纯粹性使得它随时可以安全地停止计算。

我们正在构建的每个 React 新特性都利用到了纯函数。从数据获取到动画再到性能,保持组件的纯粹可以充分释放 React 范式的能力。

请记住,React 无法保证组件函数以任何特定的顺序执行,因此你无法通过设置变量在它们之间进行通信。所有的交流都必须通过 props 进行。 

你的 mutation 保持在局部,并使你的渲染函数保持纯粹。但你仍然需要小心:例如,当你想要更改数组的任意项时,必须先对其进行拷贝。
记住数组上的哪些操作会修改原始数组、哪些不会,这非常有帮助。例如,push、pop、reverse 和 sort 会改变原始数组,但 slice、filter 和 map 则会创建一个新数组 

15.将UI视为树

当 React 应用程序逐渐成形时,许多组件会出现嵌套。那么 React 是如何跟踪应用程序组件结构的?

React 以及许多其他 UI 库,将 UI 建模为树。将应用程序视为树对于理解组件之间的关系以及调试性能和状态管理等未来将会遇到的一些概念非常有用。

你将会学习到

  • React 如何看待组件结构
  • 渲染树是什么以及它有什么用处
  • 模块依赖树是什么以及它有什么用处

15.1 将 UI 视为树 

树是项目和 UI 之间的关系模型,通常使用树结构来表示 UI。例如,浏览器使用树结构来建模 HTML(DOM)与CSS(CSSOM)。移动平台也使用树来表示其视图层次结构。

React 从组件中创建 UI 树。在这个示例中,UI 树最后会用于渲染 DOM。
标React 从组件中创建 UI 树。在这个示例中,UI 树最后会用于渲染 DOM。题

 15.2渲染树

组件的一个主要特性是能够由其他组件组合而成。在 嵌套组件 中有父组件和子组件的概念,其中每个父组件本身可能是另一个组件的子组件。

当渲染 React 应用程序时,可以在一个称为渲染树的树中建模这种关系。

 

 这棵树由节点组成,每个节点代表一个组件。例如,App、FancyText、Copyright 等都是我们树中的节点。
在 React 渲染树中,根节点是应用程序的 根组件。在这种情况下,根组件是 App,它是 React 渲染的第一个组件。树中的每个箭头从父组件指向子组件。

渲染树表示 React 应用程序的单个渲染过程。在 条件渲染 中,父组件可以根据传递的数据渲染不同的子组件。 

15.3模块依赖树

 

在 React 应用程序中,可以使用树来建模的另一个关系是应用程序的模块依赖关系。当 拆分组件 和逻辑到不同的文件中时,就创建了 JavaScript 模块,在这些模块中可以导出组件、函数或常量。

模块依赖树中的每个节点都是一个模块,每个分支代表该模块中的 import 语句。

以之前的 Inspirations 应用程序为例,可以构建一个模块依赖树,简称依赖树。

依赖树对于确定运行 React 应用程序所需的模块非常有用。在为生产环境构建 React 应用程序时,通常会有一个构建步骤,该步骤将捆绑所有必要的 JavaScript 以供客户端使用。负责此操作的工具称为 bundler(捆绑器),并且 bundler 将使用依赖树来确定应包含哪些模块。

随着应用程序的增长,捆绑包大小通常也会增加。大型捆绑包大小对于客户端来说下载和运行成本高昂,并延迟 UI 绘制的时间。了解应用程序的依赖树可能有助于调试这些问题。

16添加交互

界面上的控件会根据用户的输入而更新。例如,点击按钮切换轮播图的展示。在 React 中,随时间变化的数据被称为状态(state)。你可以向任何组件添加状态,并按需进行更新。在本章节中,你将学习如何编写处理交互的组件,更新它们的状态,并根据时间变化显示不同的效果。

本章节

  • 如何处理用户发起的事件
  • 如何用状态使组件“记住”信息
  • React 是如何分两个阶段更新 UI 的
  • 为什么状态在你改变后没有立即更新
  • 如何排队进行多个状态的更新
  • 如何更新状态中的对象
  • 如何更新状态中的数组

 16.0.响应事件

React 允许你向 JSX 中添加事件处理程序。事件处理程序是你自己的函数,它将在用户交互时被触发,如点击、悬停、焦点在表单输入框上等等。

<button> 等内置组件只支持内置浏览器事件,如 onClick。但是,你也可以创建你自己的组件,并给它们的事件处理程序 props 指定你喜欢的任何特定于应用的名称。

// Toolbar.jsx
function Toolbar({onPlay, onPause}) {
    return (
        <div>
            <Button onClick={onPlay}>Play</Button>
            <Button onClick={onPause}>Pause</Button>
        </div>
    )};

function Button({onClick, children}) {
    return (
        <button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
            {children}
        </button>
    );
}

export default Toolbar;

以上代码介绍如下:

  1. 文件说明
    这是一个名为 Toolbar.jsx 的 React 组件文件,.jsx 后缀表明它是一个包含 JSX 语法的 JavaScript 文件,用于定义 React 组件。

  2. Toolbar 组件

    • function Toolbar({onPlay, onPause}):定义了一个名为 Toolbar 的函数式组件,它接受一个包含 onPlay 和 onPause 两个属性的对象作为参数。onPlay 和 onPause 通常是函数类型的属性,用于处理按钮点击事件。
    • return 语句内部:
      • <div>:创建一个 div 元素作为容器,用于包裹下面的按钮。
      • <Button onClick={onPlay}>Play</Button>:使用 Button 组件创建一个按钮,按钮上显示文本 "Play",并将 onPlay 函数绑定到按钮的 onClick 事件上。当用户点击这个按钮时,onPlay 函数将会被调用。
      • <Button onClick={onPause}>Pause</Button>:类似地,创建另一个按钮,显示文本 "Pause",并将 onPause 函数绑定到按钮的 onClick 事件上。点击该按钮时,onPause 函数会被触发。
  3. Button 组件

    • function Button({onClick, children}):定义了一个名为 Button 的函数式组件,接受一个包含 onClick 和 children 属性的对象作为参数。onClick 是一个函数,用于处理按钮的点击事件;children 表示在按钮标签内部的内容(例如文本)。
    • return 语句内部:
      • <button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">:创建一个原生的 HTML button 元素,将 onClick 函数绑定到按钮的点击事件上,并通过 className 属性为按钮添加了一些样式,包括蓝色背景、白色文字、加粗字体、内边距和圆角。
      • {children}:在按钮内部显示传递进来的 children 内容,例如 "Play" 或 "Pause"。
  4. 导出组件
    export default Toolbar;:将 Toolbar 组件作为默认导出,这样在其他文件中可以通过 import 语句导入并使用这个组件,例如 import Toolbar from './Toolbar.jsx';

// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';
// import Copyright from './Copyright.jsx';
import Toolbar from './Toolbar.jsx';

function App() {
    // const people = [
    //     { name: '张三', gender: 'male', education: '本科', married: true, skills: '编程、设计', num: 1 },
    //     { name: '李四', gender: 'female', education: '硕士', married: false, skills: '营销、管理', num: 2 },
    //     { name: '王五', gender: 'male', education: '大专', married: true, skills: '机械维修', num: 3 },
    //     { name: '赵六', gender: 'female', education: '博士', married: false, skills: '数据分析', num: 4 }
    // ];

    return (
        // <div className="container mx-auto p-4">
        //     <h1 className="text-3xl font-bold text-center mb-8">简历列表</h1>
        //     <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        //         {people.map((person, index) => (
        //             <Avatar
        //                 key={index}
        //                 person={person}
        //                 size={80}
        //                 num={person.num}
        //             />
        //         ))}
        //     </div>
        // </div>
        <>
        {/* <RenderList />
        <TeaSet />
        <TeaGathering /> */}
        <Toolbar 
        onPlay={() => alert('Play')}
        onPause={() => alert('Pause')}/>
        {/* <Copyright year={2004} /> */}
        </>
        

    );
}

export default App;    

分解理解

 16.0.1分步理解

  1. 在 Button 组件 内部 声明一个名为 handleClick 的函数。
  2. 实现函数内部的逻辑(使用 alert 来显示消息)。
  3. 添加 onClick={handleClick} 到 <button> JSX 中。

// Button.jsx
function Button(){
    function handleClick(){
        alert('clicked');
    }
    return (
        <button onClick={handleClick} >
            Click Me
        </button>
    );
}
export default Button;
// App.jsx
import React from 'react';
import './App.css';
import Avatar from './Avatar.jsx';
import RenderList from './RenderList.jsx';
import TeaSet from './CupNo.jsx';
import { TeaGathering } from './CupNo.jsx';
// import Copyright from './Copyright.jsx';
import Toolbar from './Toolbar.jsx';
import Button from './Button.jsx';

function App() {
    return (
        <Button />
    );
}

export default App;    

结果

还可以用以下两个代码进行替换。结果一样。

// Button.jsx
function Button(){
    // function handleClick(){
    //     alert('clicked');
    
    return (
        // <button onClick={handleClick} >
        //     Click Me
        // </button>
        <button onClick={() => alert('clicked22')} >
            Click Me
        </button>
        
    );
}

export default Button;

// Button.jsx
function Button(){
    // function handleClick(){
    //     alert('clicked');
    
    return (
        // <button onClick={handleClick} >
        //     Click Me
        // </button>
        // <button onClick={() => alert('clicked22')} >
        //     Click Me
        // </button>
        <button onClick={function handleClick(){alert('clicked33')}}>333</button> 
        
    );
}

export default Button;

如果你需要3个及不固定数量的话,你需要使用本节开头处提到的方法

关于箭头函数的知识

 箭头函数,基础知识https://zh.javascript.info/arrow-functions-basics

以上方法是等效的,但要注意

16.0.2在事件处理中读取props

 由于事件处理函数声明于组件内部,因此它们可以直接访问组件的 props。示例中的按钮,当点击时会弹出带有 message prop 的 alert:

 修改本节最初的文件Toolbar.jsx

// Toolbar.jsx
// function Toolbar({onPlay, onPause}) {
//     return (
//         <div>
//             <Button onClick={onPlay}>Play</Button>
//             <Button onClick={onPause}>Pause</Button>
//         </div>
//     )};

// function Button({onClick, children}) {
//     return (
//         <button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
//             {children}
//         </button>
//     );
// }
function AlertButton({message, children}) {
    return (
        <button onClick={() => alert(message)}>{children}</button>
    );
}
function Toolbar() {
    return (
        <div>
            <AlertButton message="Play11" >Play11</AlertButton>
            <AlertButton message="Pause22" >Pause22</AlertButton>
        </div>
    );
}
export default Toolbar;

效果如下

16.0.3 将事件处理函数作为 props 传递

通常,我们会在父组件中定义子组件的事件处理函数。比如:置于不同位置的 Button 组件,可能最终执行的功能也不同 —— 也许是播放电影,也许是上传图片。

为此,将组件从父组件接收的 prop 作为事件处理函数传递,如下所示:

// Toolbar.jsx
// function Toolbar({onPlay, onPause}) {
//     return (
//         <div>
//             <Button onClick={onPlay}>Play</Button>
//             <Button onClick={onPause}>Pause</Button>
//         </div>
//     )};

function Button({onClick, children}) {
    return (
        <button onClick={onClick} className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
            {children}
        </button>
    );
}
// function AlertButton({message, children}) {
//     return (
//         <button onClick={() => alert(message)}>{children}</button>
//     );
// }
function PlayButton({movieName}) {
    function handleClick() {
        alert(`Playing ${movieName}`);
    }
    return (
        <button onClick={handleClick}>播放"{movieName}"</button>
    );
}
function Toolbar() {
    return (
        <div>
            {/* <AlertButton message="Play11" >Play11</AlertButton>
            <AlertButton message="Pause22" >Pause22</AlertButton> */}
            <PlayButton movieName="The Godfather" />
        </div>
    );
}
export default Toolbar;

效果如下:

示例中,Toolbar 组件渲染了一个 PlayButton 组件和 UploadButton 组件:

  • PlayButton 将 handlePlayClick 作为 onClick prop 传入 Button 组件内部。
  • UploadButton 将 () => alert('正在上传!') 作为 onClick prop 传入 Button 组件内部。

最后,你的 Button 组件接收一个名为 onClick 的 prop。它直接将这个 prop 以 onClick={onClick} 方式传递给浏览器内置的 <button>。当点击按钮时,React 会调用传入的函数。

如果你遵循某个 设计系统 时,按钮之类的组件通常会包含样式,但不会指定行为。而 PlayButtonUploadButton 之类的组件则会向下传递事件处理函数。

16.0.4自定义处理函数

没留意。丢了近2万字的记录。下周了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332585.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法题型讲解

一.双指针 主要分为俩种类型&#xff1a; 1.左右指针&#xff1a;双指针指向开头&#xff0c;以一定标准移动或交换&#xff0c;对区域进行划分&#xff0c;或找到特殊点的位置 &#xff08;如&#xff1a;快慢指针判断有无环&#xff0c;移动零&#xff09; 2.对撞指针&am…

Redis和数据库一致性问题

操作模拟 1、先更新数据库还是先更新缓存&#xff1f; 1.1先更新缓存&#xff0c;再更新数据库 按并发的角度来说&#xff0c;有两个线程A、B&#xff0c;操作同一个数据&#xff0c;线程A先更新缓存为1&#xff0c;在线程A更新数据库之前&#xff0c;这时候线程B进来&#…

第R8周:RNN实现阿尔茨海默病诊断(pytorch)

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营]中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊]** 本人往期文章可查阅&#xff1a; 深度学习总结 一、准备工作 &#x1f3e1; 我的环境&#xff1a; 语言环境&#xff1a;Python3.1…

C++基础精讲-02

文章目录 1.C/C申请、释放堆空间的方式对比1.1C语言申请、释放堆空间1.2C申请、释放堆空间1.2.1 new表达式申请数组空间 1.3回收空间时的注意事项1.4malloc/free 和 new/delete 的区别 2.引用2.1 引用的概念2.2 引用的本质2.3 引用与指针的联系与区别2.4 引用的使用场景2.4.1 引…

【网络安全】Linux 命令大全

未经许可,不得转载。 文章目录 前言正文文件管理文档编辑文件传输磁盘管理磁盘维护网络通讯系统管理系统设置备份压缩设备管理其它命令前言 在网络安全工作中,熟练掌握 Linux 系统中的常用命令对于日常运维、日志分析和安全排查等任务至关重要。 以下是常用命令的整理汇总,…

C++学习之ORACLE①

目录 1.ORACLE数据库简介 2..ORACLE数据库安装 3..ORACLE体系结构 4..ORACLE基本概念 5..ORACLE基本元素 6..ORACLE数据库启动和关闭 7.SQLPLUS登录ORACLE数据库相关操作 8.SQLPLUS的基本操作 9.oracle中上课使用的方案 10.SQL语言分类 11.SQL中的select语句语法和注…

企业级开发SpringBoost玩转Elasticsearch

案例 Spring Boot 提供了 spring-data-elasticsearch 模块&#xff0c;可以方便地集成 Elasticsearch。 下面我们将详细讲解如何在 Spring Boot 中使用 Elasticsearch 8&#xff0c;并提供示例代码。 1. 添加依赖: 首先&#xff0c;需要在 pom.xml 文件中添加 spring-data-e…

从零开始的图论讲解(1)——图的概念,图的存储,图的遍历与图的拓扑排序

目录 前言 图的概念 1. 顶点和边 2. 图的分类 3. 图的基本性质 图的存储 邻接矩阵存图 邻接表存图 图的基本遍历 拓扑排序 拓扑排序是如何写的呢? 1. 统计每个节点的入度 2. 构建邻接表 3. 将所有入度为 0 的节点加入队列 4. 不断弹出队头节点&#xff0c;更新其…

SpringBoot框架—启动原理

1.SpringBootApplication注解 在讲解启动原理之前先介绍一个非常重要的注解SpringBootApplication&#xff0c;这个注解在Springboot程序的入口文件Application.java中必须添加。SpringBootApplication是一个整合了三个核心注解的组合注解。 三个核心注解的作用机制&#xff1…

怎么检查网站CDN缓存是否生效

为什么要使用CDN缓存&#xff1f; 网站使用缓存可显著提升加载速度&#xff0c;减少服务器负载和带宽消耗&#xff0c;优化用户体验&#xff0c;增强架构稳定性&#xff0c;助力SEO优化&#xff0c;实现资源高效利用与性能平衡。 通过合理配置 CDN 缓存策略&#xff0c;可降低…

【自然语言处理】深度学习中文本分类实现

文本分类是NLP中最基础也是应用最广泛的任务之一&#xff0c;从无用的邮件过滤到情感分析&#xff0c;从新闻分类到智能客服&#xff0c;都离不开高效准确的文本分类技术。本文将带您全面了解文本分类的技术演进&#xff0c;从传统机器学习到深度学习&#xff0c;手把手实现一套…

vba讲excel转换为word

VBA将excel转换为word Sub ExportToWordFormatted() 声明变量Dim ws As Worksheet 用于存储当前活动的工作表Dim rng As Range 用于存储工作表的使用范围&#xff08;即所有有数据的单元格&#xff09;Dim rowCount As Long, colCount As Long 用于存储数据范围的行数和列数…

ubuntu安装openWebUI和Dify【自用详细版】

系统版本&#xff1a;ubuntu24.04LTS 显卡&#xff1a;4090 48G 前期准备 先安装好docker和docker-compose&#xff0c;可以参考我之前文章安装&#xff1a; ubuntu安装docker和docker-compose【简单详细版】 安装openWebUI 先docker下载ollama docker pull ghcr.nju.edu.c…

基于Flask的勒索病毒应急响应平台架构设计与实践

基于Flask的勒索病毒应急响应平台架构设计与实践 序言&#xff1a;安全工程师的防御视角 作为从业十年的网络安全工程师&#xff0c;我深刻理解勒索病毒防御的黄金时间法则——应急响应速度每提升1分钟&#xff0c;数据恢复成功率将提高17%。本文介绍的应急响应平台&#xff…

spark数据清洗案例:流量统计

一、项目背景 在互联网时代&#xff0c;流量数据是反映用户行为和业务状况的重要指标。通过对流量数据进行准确统计和分析&#xff0c;企业可以了解用户的访问习惯、业务的热门程度等&#xff0c;从而为决策提供有力支持。然而&#xff0c;原始的流量数据往往存在格式不规范、…

list的使用以及模拟实现

本章目标 1.list的使用 2.list的模拟实现 1.list的使用 在stl中list是一个链表,并且是一个双向带头循环链表,这种结构的链表是最优结构. 因为它的实现上也是一块线性空间,它的使用上是与string和vector类似的.但相对的因为底层物理结构上它并不像vector是线性连续的,它并没有…

【今日三题】小乐乐改数字 (模拟) / 十字爆破 (预处理+模拟) / 比那名居的桃子 (滑窗 / 前缀和)

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;每日两三题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 小乐乐改数字 (模拟)十字爆破 (预处理模拟&#xff09;比那名居的桃子 (滑窗 / 前缀和) 小乐乐改数字 (模拟) 小乐乐改数字…

基于 Qt 的图片处理工具开发(一):拖拽加载与基础图像处理功能实现

一、引言 在桌面应用开发中&#xff0c;图片处理工具的核心挑战在于用户交互的流畅性和异常处理的健壮性。本文以 Qt为框架&#xff0c;深度解析如何实现一个支持拖拽加载、亮度调节、角度旋转的图片处理工具。通过严谨的文件格式校验、分层的架构设计和用户友好的交互逻辑&am…

44、Spring Boot 详细讲义(一)

Spring Boot 详细讲义 目录 Spring Boot 简介Spring Boot 快速入门Spring Boot 核心功能Spring Boot 技术栈与集成Spring Boot 高级主题Spring Boot 项目实战Spring Boot 最佳实践总结 一、Spring Boot 简介 1. Spring Boot 概念和核心特点 1.1、什么是 Spring Boot&#…

虽然理解git命令,但是我选择vscode插件!

文章目录 2025/3/11 补充一个项目一个窗口基本操作注意 tag合并冲突已有远程&#xff0c;新加远程仓库切换分支stash 只要了解 git 的小伙伴&#xff0c;应该都很熟悉这些指令&#xff1a; git init – 初始化git仓库git add – 把文件添加到仓库git commit – 把文件提交到仓库…