React学习[一]
- 1 UI描述
- 1.1 组件的创建与使用
- 1.1.1 创建组件
- 1.1.2 使用组件
- 1.2 组件的导入与导出
- 1.2.1 根组件文件
- 1.2.2 导出和导入一个组件
- 1.2.3 从同一文件中导出和导入多个组件
- 1.3 使用JSX书写标签语言
- 1.3.1 JSX:将标签引入JavaScript
- 1.3.2 将HTML转换为JSX
- 1.3.3 高级提示:使用JSX转化器
- 1.4 在JSX中通过大括号使用JavaScript
- 1.4.1 使用引号传递字符串
- 1.4.2 使用"双大括号":JSX中的CSS和对象
- 1.5 将Props传递给组件
- 1.5.1 将Props传递给组件
- 1.5.2 给props指定一个默认值
- 1.5.3 使用JSX展开语法传递props
- 1.5.4 将JSX作为子组件传递
- 1.5.5 props如何随时间变化
- 1.6 条件渲染
- 1.6.1 条件返回JSX
- 1.6.2 选择性地返回null
- 选择性地包含JSX
- 选择性地将JSX赋值给变量
- 1.7 渲染列表
- 1.7.1 从数组中渲染数据
- 1.7.2 对数组项进行过滤
- 1.7.3 用key保持列表项的顺序
- 1.8 保持组件为纯函数
- 1.8.1 局部`mutation`
1 UI描述
1.1 组件的创建与使用
组件是用户界面UI的构成要素。创建一个组件可以分为三步:
1.1.1 创建组件
-
导出组件
export default
-
定义函数
-
function name(props) {}
-
定义组件的时候,组件的名称必须以大写字母开头,否则将无法运行。
HTML
标签是小写的
-
-
添加标签
-
JSX
(使用JavaScript
) -
TSX
(使用TypeScript
) -
注意:如果标签和
return
不在同一行,则必须把它包裹在一堆括号中,没有括号包裹的话,任何在
return
下一行的代码都将被忽略!
-
1.1.2 使用组件
在父组件中引入即可,但是不要在组件中定义组件。应该在顶层定义组件。当子组件需要使用父组件的数据时,需要 通过 props 的形式进行传递,而不是嵌套定义。
1.2 组件的导入与导出
1.2.1 根组件文件
相当于把所有模块化组件做一个集成的文件。
1.2.2 导出和导入一个组件
对组件进行拆分可以分为三步:
- 创建一个新的JS文件来存放该组件
- 导出该文件中的函数组件(可以使用默认导出或者具名导出)
- 在需要使用该组件的文件中导入(可以根据相应的导出的方式使用默认导入或者具名导入)
语法 | 导出语句 | 导入语句 |
---|---|---|
默认 | export default function Hello() {} | import Hello from './Hello.js'; |
具名 | export function Hello() {} | import { Hello } from './Hello.js'; |
注意:在引入一些文件时虽然没有添加.js
文件名后缀,比如./Hello.js
还是./Hello
在React
里都能正常使用,只是前者更符合原生ES模块
一个文件里有且仅有一个默认导出,但是可以有任意多个具名导出。组件的导出方式决定了其导入方式。通常,文件中仅包含一个组件时,会选择默认导出,而当文件中包含多个组件或某个值需要导出时,则会选择具名导出。
1.2.3 从同一文件中导出和导入多个组件
同一文件中,有且仅有一个默认导出,但可以有多个具名导出!
1.3 使用JSX书写标签语言
JSX是JavaScript语法扩展,可以在JavaScript文件中写类似HTML的标签。(虚拟DOM
)
1.3.1 JSX:将标签引入JavaScript
**在 React 中,渲染逻辑和标签共同存在于同一个地方——组件。**将一个按钮的渲染逻辑和标签放在一起可以确保它们在每次编辑时都能保持互相同步。每个 React
组件都是一个 JavaScript
函数,它会返回一些标签,React 会将这些标签渲染到浏览器上。
JSX
和 React
是相互独立的东西。JSX 是一种语法扩展,而 React 则是一个 JavaScript 的库。
1.3.2 将HTML转换为JSX
JSX规则:
-
只能返回一个根元素
-
如果想要在一个组件中包含多个元素,需要用一个父标签把它们包裹起来。
-
如果不想在标签中增加一个额外的
<div>
,可以用<>
和</>
元素来代替:这个空标签被称作 Fragment. React Fragment 允许将子元素分组,而不会在 HTML 结构中添加额外节点。
为什么多个 JSX 标签需要被一个父元素包裹?
答:JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
-
-
标签必须闭合
-
使用驼峰式命名法给大部分属性命名
- 由于历史原因,
aria-*
和data-*
属性是以带-
符号的 HTML 格式书写的。
- 由于历史原因,
1.3.3 高级提示:使用JSX转化器
将HTML转换为JSX:https://transform.tools/html-to-jsx
1.4 在JSX中通过大括号使用JavaScript
1.4.1 使用引号传递字符串
想把一个字符串属性传递给 JSX 时,把它放到单引号或双引号中
若想要动态指定可以使用{}
大括号内的任何JavaScript表达式都可以正常运行。
在 JSX 中,只能在以下两种场景中使用大括号:
- 用作 JSX 标签内的文本:
<h1>{name}'s To Do List</h1>
是有效的,但是<{tag}>Gregorio Y. Zara's To Do List</{tag}>
无效。 - 用作紧跟在
=
符号后的 属性:src={avatar}
会读取avatar
变量,但是src="{avatar}"
只会传一个字符串{avatar}
。
1.4.2 使用"双大括号":JSX中的CSS和对象
对象也用大括号表示,例如 { name: "无敌是多么寂寞", inventions: 5 }
。因此,为了能在 JSX 中传递,你必须用另一对额外的大括号包裹对象:person={{ name: "无敌是多么寂寞", inventions: 5 }}
。
import { getImageUrl } from './utils.js'const person = {name: 'Gregorio Y. Zara',imageId: '7vQD0fP',imageSize: 's',theme: {backgroundColor: 'black',color: 'pink'}
};export default function TodoList() {return (<div style={person.theme}><h1>{person.name}'s Todos</h1><imgclassName="avatar"src={getImageUrl(person)}alt={person.name}/><ul><li>Improve the videophone</li><li>Prepare aeronautics lectures</li><li>Work on the alcohol-fuelled engine</li></ul></div>);
}
// 将获取图片的语句封装成一个函数
export function getImageUrl(person) {return ('https://i.imgur.com/' +person.imageId +person.imageSize +'.jpg');
}
1.5 将Props传递给组件
React
组件使用 props 来互相通信。每个父组件都可以提供 props
给它的子组件,从而将一些信息传递给它。Props
可能会让你想起 HTML
属性,但你可以通过它们传递任何 JavaScript
值,包括对象、数组和函数。
1.5.1 将Props传递给组件
父组件可以分两步将props传递给子组件
-
首先在父组件中将
props
传递给引用的子组件,使得子组件可以读取这些props
-
在子组件中读取
props
,这些 props 在({
和})
之间,并由逗号分隔。比如function Avatar({ person, size }) {// 在这里 person 和 size 是可被子组件访问的 }
props
正是 组件的唯一参数! React 组件函数接受一个参数,一个 props
对象。比如:
function Avatar(props) {let person = props.person;let size = props.size;// ...
}
1.5.2 给props指定一个默认值
如果想在没有指定值的情况下给 prop
一个默认值,可以通过在参数后面写 = 默认值
来进行解构,比如:
function Avatar({ person, size = 100 }) {// ...
}
但是如果传递了 size={null}
或 size={0}
,默认值将 不 被使用。
1.5.3 使用JSX展开语法传递props
先看两段代码感受一下
// 第一段
function Profile({ person, size, isSepia, thickBorder }) {return (<div className="card"><Avatarperson={person}size={size}isSepia={isSepia}thickBorder={thickBorder}/></div>);
}
// 第二段
function Profile(props) {return (<div className="card"><Avatar {...props} /></div>);
}
是不是觉得第二段用了展开语法的代码更简洁!但是这通常表示你应该拆分组件了,并需要将子组件作为JSX传递。
1.5.4 将JSX作为子组件传递
当将内容嵌套在 JSX 标签中时,父组件将在名为 children
的 prop 中接收到该内容。
function Card({ children }) {return (<div className="card">{children}</div>);
}export default function Profile() {return (<Card><h1>哎哟不错哦</h1></Card>);
}
可以将带有 children
prop 的组件看作有一个“洞”,可以由其父组件使用任意 JSX 来“填充”。
1.5.5 props如何随时间变化
**一个组件可能会随着时间的推移收到不同的 props
。**但是props
的改变是父组件通过传递不同的props
——新对象来改变的。旧props
会被丢弃然后被JS引擎回收它们占用的内存.当需要交互性时,可以设置 state
。
1.6 条件渲染
1.6.1 条件返回JSX
可以用if else、&&、? :
语句来写分支逻辑
1.6.2 选择性地返回null
例如:
function Item({ name, isPacked }) {if (isPacked) {return null;}return <li className="item">{name}</li>;
}export default function PackingList() {return (<section><h1>Sally Ride 的行李清单</h1><ul><Item isPacked={true} name="宇航服" /><Item isPacked={true} name="带金箔的头盔" /><Item isPacked={false} name="Tam 的照片" /></ul></section>);
}
实际上,在组件里返回 null
并不常见。通常情况下,可以在父组件里选择是否要渲染该组件。
选择性地包含JSX
可以使用三目运算符? :
。如果代码有小段重复可以考虑用这个进行简单的条件判断。比如:
// 修改前
if (isPacked) {return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;// 修改后
return (<li className="item">{isPacked ? name + ' ✔' : name}</li>
);
或者用&&
运算符
return (<li className="item">{name} {isPacked && '✔'}</li>);
与表达式的判定若为false
,React会将其视为控制,不会对其进行任何渲染。但切勿把数字放在&&
左侧,因为如果左侧是0
,React会渲染0
。
选择性地将JSX赋值给变量
结合if
语句和let
赋值变量可以更加灵活。因为可以使用 let
进行重复赋值,所以一开始可以将想展示的(这里指的是物品的名字)作为默认值赋予给该变量。
function Item({ name, isPacked }) {let itemContent = name;if (isPacked) {itemContent = name + " ✔";}return (<li className="item">{itemContent}</li>);
}export default function PackingList() {return (<section><h1>Sally Ride 的行李清单</h1><ul><Item isPacked={true} name="宇航服" /><Item isPacked={true} name="带金箔的头盔" /><Item isPacked={false} name="Tam 的照片" /></ul></section>);
}
1.7 渲染列表
操作数组中的数据,将一个数据集渲染成多个相似的组件。
1.7.1 从数组中渲染数据
利用map()
函数
const people = ['凯瑟琳·约翰逊: 数学家','马里奥·莫利纳: 化学家','穆罕默德·阿卜杜勒·萨拉姆: 物理学家','珀西·莱温·朱利亚: 化学家','苏布拉马尼扬·钱德拉塞卡: 天体物理学家',
];export default function List() {const listItems = people.map(person =><li>{person}</li>);return <ul>{listItems}</ul>;
}
注意:这里还未给每个数据加上唯一标识符key
1.7.2 对数组项进行过滤
先利用filter()
函数进行筛选,再利用map()
函数进行渲染
import { people } from './data.js';
import { getImageUrl } from './utils.js';export default function List() {const chemists = people.filter(person =>person.profession === '化学家');const listItems = chemists.map(person =><li><imgsrc={getImageUrl(person)}alt={person.name}/><p><b>{person.name}:</b>{' ' + person.profession + ' '}因{person.accomplishment}而闻名世界</p></li>);return <ul>{listItems}</ul>;
}
注意:这里还未给每个数据加上唯一标识符key
.
箭头函数会隐式地返回位于 =>
之后的表达式,所以可以省略 return
语句。不过,如果 =>
后面跟了一对花括号 {
,那必须使用 return
来指定返回值! 箭头函数 => {
后面的部分被称为 “块函数体”,块函数体支持多行代码的写法,但要用 return
语句才能指定返回值。
1.7.3 用key保持列表项的顺序
必须给数组中的每一项都指定一个 key
——它可以是字符串或数字的形式,只要能唯一标识出各个数组项就行。
直接放在
map()
方法里的 JSX 元素一般都需要指定key
值!
一个合适的 key
可以帮助 React 推断发生了什么,从而得以正确地更新 DOM 树。
如果想让每个列表项都输出多个 DOM 节点而非一个的话,只能要么把生成的节点用一个 <div>
标签包裹起来,要么使用长一点但更明确的 <Fragment>
写法:
import { Fragment } from 'react';// ...const listItems = people.map(person =><Fragment key={person.id}><h1>{person.name}</h1><p>{person.bio}</p></Fragment>
);
这里的 Fragment 标签本身并不会出现在 DOM 上,这串代码最终会转换成 <h1>
、<p>
、<h1>
、<p>
…… 的列表。
key值的设定:
- 来自数据库的数据:可以直接使用数据表中的主键
- 本地产生数据:可以使用一个自增计数器或者一个类似
uuid
的库来生成 key。
key需要满足的条件:
- key 值在兄弟节点之间必须是唯一的。 不过不要求全局唯一,在不同的数组中可以使用相同的 key。
- key 值不能改变。不要在运行过程中动态地产生 key。
React之所以需要key
值,是因为即使元素的位置在渲染的过程中发生了改变,它提供的 key
值也能让 React 在整个生命周期中一直认得它。
注意,组件不会把
key
当作 props 的一部分。Key 的存在只对 React 本身起到提示作用。如果你的组件需要一个 ID,那么请把它作为一个单独的 prop 传给组件:<Profile key={id} userId={id} />
。
挑战题:
请根据给你的数组生成菜谱列表!其中每个菜谱,都用 <h2>
来显示它的名称,并在 <ul>
里列出它所需的原料。
data.js
export const recipes = [{id: 'greek-salad',name: '希腊沙拉',ingredients: ['西红柿', '黄瓜', '洋葱', '油橄榄', '羊奶酪'],},{id: 'hawaiian-pizza',name: '夏威夷披萨',ingredients: ['披萨饼皮', '披萨酱', '马苏里拉奶酪', '火腿', '菠萝'],},{id: 'hummus',name: '鹰嘴豆泥',ingredients: ['鹰嘴豆', '橄榄油', '蒜瓣', '柠檬', '芝麻酱'],},
];
App.js
import { recipes } from './data.js';function RecipeShow({ recipesList }) {return (<div>{recipesList.map(recipe =><div key={recipe.id}><h2>{recipe.name}</h2><ul>{recipe.ingredients.map(ingredent => <li key={ingredent}>{ingredent}</li>)} </ul></div>)}</div>)
}function Card({children}) {return (<div>{ children }</div>)
}export default function RecipeList() {return (<Card><div><h1>菜谱</h1><RecipeShow recipesList={recipes} /></div></Card>);
}
1.8 保持组件为纯函数
一个React组件,不管什么时候给了它一个相同的输入,那么它的输出就是相同的,就像数学公式一样。比如y=2x
,无论何时传x=3
,其输出总是y=6
。
React 无法保证组件函数以任何特定的顺序执行,因此无法通过设置变量在它们之间进行通信。所有的交流都必须通过 props 进行。
1.8.1 局部mutation
我们将组件改变了预先存在的变量的值的现象称为突变(mutation)
。局部突变的意思就是在渲染时更改刚刚创建的变量和对象,属于内部突变,不会影响函数外部的变量或函数。
但是事件处理程序无需是纯函数,包括更新屏幕、启动动画、更改数据等。
React为何侧重于纯函数?
- 复用性,适应不同环境
- 可以缓存纯函数的输出来跳过渲染,提高性能
- 在渲染深层组件树的过程中,如果数据发生了变化,可以随时安全停止计算,节省时间。