重学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 本身起到提示作用

纯函数

相关推荐
云端看世界6 分钟前
为什么要学习 ECMAScript 协议
前端·javascript·ecmascript 6
9179 分钟前
无缝轮播图实现:从原理到实践
前端
我爱鸿蒙开发18 分钟前
🥇聊聊鸿蒙的一端开发,多端部署。
前端·开源·harmonyos
前端付杰18 分钟前
深入理解 IndexedDB:索引与游标查询的高效应用
前端·javascript·indexeddb
best66619 分钟前
前端项目SVG展示方案总结,以Vue3+TS为例
前端
啊花是条龙19 分钟前
Angular 开发指南:组件、数据绑定、指令、服务、HTTP、路由和表单
前端·angular.js
小桥风满袖20 分钟前
Three.js-硬要自学系列12 (各种贴图的综合应用)
前端·css·three.js
溪饱鱼22 分钟前
秒杀传统数据库!Cloudflare D1 + Drizzle组合拳,高并发高可用,让我们的成本爆降10倍 - D1
前端·后端
Dgua23 分钟前
小程序开发background-image不显示的问题
前端·抖音小程序
作曲家种太阳23 分钟前
第二章节 响应式原理介绍-【手摸手带你实现一个vue3】
前端