重学React(一):描述UI

背景:React现在已经更新到19了,文档地址也做了全面的更新,上一次系统性的学习还是在16-17的大版本更新。所以,现在就开始重新学习吧~

学习内容:

  1. React官网教程:https://zh-hans.react.dev/learn/describing-the-ui
  2. 其他辅助资料(看到再补充)
    补充说明:这次学习更多的是以学习笔记的形式记录,看到哪记到哪

基础知识

React 应用是由被称为 组件 的独立 UI 片段构建而成。React 组件本质上是可以任意添加标签的 JavaScript 函数

React 允许你将标签、CSS 和 JavaScript 组合成自定义"组件",即 应用程序中可复用的 UI 元素

React 组件是一段可以使用标签进行扩展 的 JavaScript 函数,组件的名称必须以大写字母开头(React的语法规定,这样它才能分清是React组件还是正常的html标签)

js 复制代码
// export default 导出声明
// function Profile 定义函数,function名必须首字母大写

export default function Profile() {
// return 如果换行,就必须用()将内容包裹
// 没有括号包裹的话,任何在 return 下一行的代码都将被忽略!
  return (
    <img
      src="https://i.imgur.com/MK3eW3Am.jpg"
      alt="Katherine Johnson"
    />
  )
  // 或者 标签只有一行的时候括号可以省略
   return <img src="https://i.imgur.com/MK3eW3Am.jpg" alt="Katherine Johnson" />
  )
}

// 你可以只定义组件一次,然后按需多处和多次使用
export default function Gallery() {
  return (
    <section>
      <h1>了不起的科学家</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

// 组件不建议嵌套组件定义,不然会很慢并且可能会有bug产生
export default function Gallery() {
  // 🔴 永远不要在组件中定义组件
  function Profile() {
    // ...
  }
  // ...
}

// 使用时可以import './Gallery.js' 或者 './Gallery',在 React 里都能正常使用,只是前者更符合 原生 ES 模块
import Gallery from './Gallery';
import Gallery from './Gallery.js';

导出方式

默认导出 vs 具名导出

一个文件里有且仅有一个 默认 导出,但是可以有任意多个 具名 导出

当使用默认导入时,可以在 import 语句后面进行任意命名。比如 import Banana from './Button.js'。相反,对于具名导入,导入和导出的名字必须一致。

同一文件中,有且仅有一个默认导出,但可以有多个具名导出

语法 导出语句 导入语句
默认 export default function Button() {} import Button from './Button.js';
具名 export function Button() {} import { Button } from './Button.js';

JSX

  1. 只能返回一个根元素
    在一个组件中包含多个元素,需要用一个父标签把它们包裹起来,如果不想添加新的dom元素,可以使用<>...</>(Fragment),React Fragment 允许将子元素分组,而不会在 HTML 结构中添加额外节点
    原因:JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来
  2. 标签必须闭合
    这是强制规定,要么自闭合(<img />),要么添加闭合标签(<li>...</li>)
  3. 使用驼峰式命名法给大部分属性命名
    JSX 最终会被转化为 JavaScript,而 JSX 中的属性也会变成 JavaScript 对象中的键值对。组件经常会遇到需要用变量的方式读取这些属性的时候。但 JavaScript 对变量的命名有限制,所以需要避开这些限制
    1. 变量名称不能包含 - 符号,所以属性大部分用驼峰
    2. 变量不能用保留字如class,所以在jsx中用className代替
    3. 由于历史原因,aria-* 和 data-* 属性是以带 - 符号的 HTML 格式书写的

需要将一个字符串属性传递给 JSX 时,把它放到单引号或双引号

js 复制代码
// "" 引号中的内容按照字符串的形式处理,单引号双引号都可以,但使用双引号会多点
// {} 大括号中的内容会被动态引用,直接在标签中使用 JavaScript,可以在其他地方声明,在使用的时候直接读取js对应的值
// 大括号内的任何 JavaScript 表达式都能正常运行
export default function Avatar() {
const alt = "Gregorio Y. Zara"
//
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/7vQD0fPs.jpg"
      alt={alt}
    />
  );
}

大括号使用场景:

  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}

在JSX中还可以传递对象,对象也是用大括号表示,所以要引用对象的时候就需要使用两个括号

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

Props

React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它,包括对象、数组和函数等

js 复制代码
import { getImageUrl } from './utils.js';
// props是组件的唯一参数
// function里使用大括号获取props是解构
// 也可以写成这样
// function Avatar(props) {
//	const person = props.person
//	const size = props.size
// }
// 如果你想在没有指定值的情况下给 prop 一个默认值,可以通过在参数后面写 = 和默认值来进行解构
// 默认值仅在缺少 size prop 或 size={undefined} 时生效,等于null都不行
function Avatar({ person, size=100 }) {
// person 和 size 是可访问的
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
// 这样使用不同的参数,就能展示出两个类似但是又独立的组件,这就是组件复用一个很重要的意义
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Aklilu Lemma', 
          imageId: 'OKS67lh'
        }}
      />
    </div>
  );
}

// 还可以使用 JSX 展开语法传递 props 
// 像这个场景,props里面所有的内容都是需要传递到Avatar组件中时,就可以直接用展开语法传递
function Profile({ person, size, isSepia, thickBorder }) {
  return (
    <div className="card">
      <Avatar
        person={person}
        size={size}
        isSepia={isSepia}
        thickBorder={thickBorder}
      />
    </div>
  );
}
// 可以写成这样
function Profile(props) {
  return (
    <div className="card">
      <Avatar {...props} />
    </div>
  );
}
// 假设Profile 中isSepia不需要传递,其他都需要,还可以写成这样
function Profile({isSepia, ...rest}) {
  return (
    <div className="card">
      <Avatar {...rest} />
    </div>
  );
}

当你将内容嵌套在 JSX 标签中时,父组件将在名为 children 的 prop 中接收到该内容

通俗的来说就是某个组件标签内容,在接收的时候会自动处理成children的props
<Aaa><div>里面是一系列的内容</div> </Aaa>,在声明Aaa这个组件时,有一个隐藏props,children,表示的就是div及其包裹的内容

可以将带有 children prop 的组件看作有一个"洞",可以由其父组件使用任意 JSX 来"填充"

js 复制代码
import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

props 是 不可变的。当一个组件需要改变它的 props(例如,响应用户交互或新数据)时,它不得不"请求"它的父组件传递 不同的 props ------ 一个新对象!它的旧 props 将被丢弃,最终 JavaScript 引擎将回收它们占用的内存

Props 是只读的时间快照:每次渲染都会收到新版本的 props

你不能改变 props。当你需要交互性时,你可以设置 state。

渲染

在 React 中,可以通过使用 JavaScript 的 if 语句、&& 和 ? : 运算符来选择性地渲染 JSX

  1. if /else
js 复制代码
// 这段代码的意思是如果这个组件引用的时候返回了isPacked,那展示的时候就带勾,否则就不带
// 这种代码很适合使用在非黑即白的场景下,如果是,就展示A,其他情况都展示B
// 注意两种情况下都需要写return
function Item({ name, isPacked }) {
  if (isPacked) {
  return <li className="item">{name} ✅</li>;
}
return <li className="item">{name}</li>;
}

// 还可以写成这样
// 这样虽然冗长,但最灵活
function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = name + " ✅";
  }
  return (
    <li className="item">
      {itemContent}
    </li>
  );
}

在一些情况下,你不想有任何东西进行渲染。比如,你不想显示已经打包好的物品。但一个组件必须返回一些东西。这种情况下,你可以直接返回 null

一般来说,尽量不要在子组件里返回null,而是由父组件控制是否渲染

  1. 三目运算符(? :)

有时候可能需要更加紧凑的形式来进行条件渲染,这个时候三目运算符就可以使用了

js 复制代码
// 这种写法和上面if/else的写法是完全一样的,因为JSX 元素不是"实例",它们没有内部状态也不是真实的 DOM 节点
function Item({ name, isPacked }) {
return (
  <li className="item">
    {isPacked ? name + ' ✅' : name}
  </li>
);
}
  1. 与运算符(&&)
    在 React 组件里,通常用在当条件成立时,想渲染一些 JSX,或者不做任何渲染,这时候使用&&
    &&运算符左侧(我们的条件)为 true 时,它则返回其右侧的值(在我们的例子里是勾选符号)。但如果左侧是 false,则整个表达式会变成 false

逻辑与(&&)运算符从左到右对操作数求值,遇到第一个假值操作数时立即返回;如果所有的操作数都是真值,则返回最后一个操作数的值

在 JSX 里,React 会将 false 视为一个"空值",不会被渲染

⚠️⚠️⚠️ 如果左侧是 0,整个表达式将变成左侧的值(0),React 此时则会渲染 0 而不是不进行渲染(这个记得和常规的JS进行区分)

js 复制代码
// 上面的例子也可以表现成,isPacked,才展示勾,就可以写成这样
return (
  <li className="item">
    {name} {isPacked && '✅'}
  </li>
);

渲染列表

我们也可以像在js那样使用数组的方法在jsx里渲染列表,目前示例用的filter和map

js 复制代码
const people = [{
  id: 0,
  name: '凯瑟琳·约翰逊',
  profession: '数学家',
}, {
  id: 1,
  name: '马里奥·莫利纳',
  profession: '化学家',
}, {
  id: 2,
  name: '穆罕默德·阿卜杜勒·萨拉姆',
  profession: '物理学家',
}, {
  id: 3,
  name: '珀西·莱温·朱利亚',
  profession: '化学家',
}, {
  id: 4,
  name: '苏布拉马尼扬·钱德拉塞卡',
  profession: '天体物理学家',
}];
export default function List() {
// 这里根据具体的业务需求,用fill,reduce,concat等等都可以,并不局限于filter
  const chemists = people.filter(person =>
    person.profession === '化学家'
  );
  // 箭头函数会隐式地返回位于 => 之后的表达式,所以这里可以省略 return 语句
  // 但如果箭头函数后面带了大括号,就必须使用return
  // const listItems = chemists.map(person => {
  // return <li>...</li>;
  // });
  // 箭头函数 => { 后面的部分被称为 "块函数体",块函数体支持多行代码的写法,但要用 return 语句才能指定返回值。假如忘了写 return,函数什么都不会返回
  const listItems = chemists.map(person =>
  // 直接放在 map() 方法里的 JSX 元素一般都需要指定 key 值
  // key 会告诉 React,每个组件对应着数组里的哪一项,所以 React 可以把它们匹配起来。这在数组项进行移动(例如排序)、插入或删除等操作时非常重要
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        因{person.accomplishment}而闻名世界
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}
// 如果需要渲染多个元素,可以用div包裹,也可以用Fragment,但是不能用<></>这种简写形式,因为它不支持添加key
// Fragment不会出现在DOM上
const listItems = people.map(person =>
  <Fragment key={person.id}>
    <h1>{person.name}</h1>
    <p>{person.bio}</p>
  </Fragment>
);

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

key 会告诉 React,每个组件对应着数组里的哪一项,所以 React 可以把它们匹配起来。这在数组项进行移动(例如排序)、插入或删除等操作时非常重要

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

为什么React需要Key

  1. key可以作为数组中的唯一标识,即使元素的位置在渲染的过程中发生了改变,它提供的 key 值也能让 React 在整个生命周期中一直认得它
  2. 最好不要使用index作为key,数组项在插入、删除或者重新排序等操作中发生改变时,可能会引起一些奇奇怪怪的bug(这个时候index会产生一些变化)
  3. 不要在运行过程中动态地产生 key,像是 key={Math.random()} 这种方式。这会导致每次重新渲染后的 key 值都不一样,从而使得所有的组件和 DOM 元素每次都要重新创建
  4. 组件不会把 key 当作 props 的一部分。Key 的存在只对 React 本身起到提示作用

纯函数

相关推荐
Dragon Wu14 分钟前
前端 下载后端返回的二进制excel数据
前端·javascript·html5
北海几经夏20 分钟前
React响应式链路
前端·react.js
晴空雨1 小时前
React Media 深度解析:从使用到 window.matchMedia API 详解
前端·react.js
一个有故事的男同学1 小时前
React性能优化全景图:从问题发现到解决方案
前端
探码科技1 小时前
2025年20+超实用技术文档工具清单推荐
前端
Juchecar1 小时前
Vue 3 推荐选择组合式 API 风格(附录与选项式的代码对比)
前端·vue.js
uncleTom6661 小时前
# 从零实现一个Vue 3通用建议选择器组件:设计思路与最佳实践
前端·vue.js
影i1 小时前
iOS WebView 异步跳转解决方案
前端
Nicholas681 小时前
flutter滚动视图之ScrollController源码解析(三)
前端
爪洼守门员1 小时前
安装electron报错的解决方法
前端·javascript·electron