Skip to content

Commit

Permalink
feat: 初始化
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangdong committed Aug 12, 2024
1 parent e1cfb0e commit 6c5d544
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 17 deletions.
4 changes: 4 additions & 0 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export default defineConfig({
text: "state",
link: "/frame/react/state",
},
{
text: "状态管理",
link: "/frame/react/manage",
},
],
"/Web-Api/": [
{
Expand Down
Binary file added frame/react/imgs/a.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions frame/react/manage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
outline: deep
layout: doc
---
## 声明式 UI 与命令式 UI 的比较
116 changes: 102 additions & 14 deletions frame/react/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@
outline: deep
layout: doc
---
## react组件
就是一个返回jsx的函数,在jsx中可以写类似html的东西
jsx 看起来很像 htmlL,但它更严格一些,并且可以显示动态信息。要求所有标签明确的闭合
```js
function MyButton() {
return (
<button>I'm a button</button>
);
}
```
:::warning :warning:
React 组件名称必须始终以大写字母开头,而 HTML 标签必须小写
:::
## jsx
* 只能返回一个根节点
* 标签必须闭合
* 使用驼峰式命名法给大部分属性命名!除了aria-* 和 data-* 属性是以带 - 符号的 HTML 格式书写的

:::tip DEEP DIVE
:::details 为什么只能返回一个根节点
Expand All @@ -41,7 +32,44 @@ const MyComponent = (props) => {
```
这样是不行的
:::
## 展示数据
## 组件
就是一个返回jsx的函数,在jsx中可以写类似html的东西

jsx 看起来很像 htmlL,但它更严格一些,并且可以显示动态信息。
```js
function MyButton() {
return (
<button>I'm a button</button>
);
}
```
:::warning :warning:
React 组件名称必须始终以大写字母开头,而 HTML 标签必须小写
:::
组件可以渲染其他组件,但是 请不要嵌套他们的定义:
```js
export default function Gallery() {
// 🔴 永远不要在组件中定义组件
function Profile() {
// ...
}
// ...
}
```
上面这段代码 非常慢,并且会导致 bug 产生。因此,你应该在顶层定义每个组件:
```js
export default function Gallery() {
// ...
}
// ✅ 在顶层声明组件
function Profile() {
// ...
}
```
当子组件需要使用父组件的数据时,你需要 通过 props 的形式进行传递,而不是嵌套定义。
## 在 jsx 中通过大括号使用 js
jsx里面可以放入html,如果需要重新跳回js,可以使用大括号包裹
```js{3}
return (
Expand Down Expand Up @@ -73,6 +101,28 @@ return (
* 作为JSX 标签内的文本`<h1>{name}'s To Do List</h1>`:可以,但`<{tag}>Gregorio Y. Zara's To Do List</{tag}>` 不会。
* 作为紧跟在符号后面的属性=:`src={avatar}`将读取avatar变量,但`src="{avatar}"`会传递字符串"{avatar}"。
## 使用 jsx 展开语法传递 props
```js
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
```
## 将 jsx 作为子组件传递(插槽)
通过解构children
```js
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
```
## 条件渲染
```jsx
<div>
Expand All @@ -89,6 +139,44 @@ return (
{isLoggedIn && <AdminPanel />}
</div>
```
## 将 jsx 赋值给变量
```jsx{4-8}
function Item({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = (
<del>
{name + " ✔"}
</del>
);
}
return (
<li className="item">
{itemContent}
</li>
);
}
```
## Fragment
Fragment 语法的简写形式 <> </> 无法接受 key 值,所以你只能要么把生成的节点用一个 `<div>` 标签包裹起来,要么使用长一点但更明确的 `<Fragment>` 写法:
```js
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>`…… 的列表。
## 严格模式
在严格模式下开发时,它将会调用每个组件函数两次
严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用 `<React.StrictMode>` 包裹根组件
## 响应事件
onClick={handleClick}末尾没有括号
## 简单示例
Expand Down
81 changes: 78 additions & 3 deletions frame/react/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ useState 在调用时没有任何关于它引用的是哪个 state 变量的信
Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最顶层调用。 你不能在条件语句、循环语句或其他嵌套函数内调用 Hook。
:::

再看这个例子
再看这个例子,连续使用三次set

<iframe src="https://codesandbox.io/embed/g7f575?view=editor+%2B+preview"
style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;"
title="一次性三次set"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
:::details :rocket: DEEP DIVE

设置 state 只会为下一次渲染变更 state 的值
Expand All @@ -98,8 +99,7 @@ Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最
尽管你调用了三次 setNumber(number + 1),但在 这次渲染的 事件处理函数中 number 会一直是 0,所以你会三次将 state 设置成 1。
:::

再看这个

再看这个,使用定时器输出state
<iframe src="https://codesandbox.io/embed/x9692v?view=editor+%2B+preview&module=%2Fsrc%2FApp.js"
style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;"
title="随时间变化的state"
Expand All @@ -112,3 +112,78 @@ state 的值在渲染过程中永远不会改变

即使其事件处理程序的代码是异步的。在该渲染 中,即使在调用之后,onClick的值number仍然保持不变。当 React 通过调用您的组件“拍摄 UI 快照”时,其值已“固定”
:::
## 批处理
`React会等到事件处理程序中的所有代码都运行完毕后才处理状态更新`

但是不会批处理多个有意的event

例如有多个点击事件,是不会进行批处理

test 点击按钮等待的数量加一,三秒后完成加一,等待减一,现在多次点击等待一会,等待变成-1
<iframe src="https://codesandbox.io/embed/c8cxxs?view=editor+%2B+preview&module=%2Fsrc%2FApp.js&hidenavigation=1"
style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;"
title="延时处理"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
:::details :thinking:

![alt text](./imgs/a.jpg)

改为传递更新函数,而不是将计数器设置为在点击期间确定的具体值

这可以确保你在增加或减少计数器时是根据其 `最新 的 state` 而不是`点击时的 state `来进行增减的。
:::
## useState也可以传人一个函数
例如
```js
setNumber(n => n + 1)
```
这会根据队列中的前一个状态计算下一个状态

```js
setNumber(n+1)
setNumber(n+1)
setNumber(n+1)

setNumber(n=>n+1)
setNumber(n=>n+1)
setNumber(n=>n+1)
```

替换状态后更新状态

```js
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}></button>

```
:::details :rocket:
6

* setNumber(number + 5):number是0,所以setNumber(0 + 5)。React 将“替换为5”添加到其队列中。
* setNumber(n => n + 1):n => n + 1是一个更新函数。React将该函数添加到其队列中。
:::

## 将 state 视为只读的
`把所有存放在 state 中的 JavaScript 对象都视为只读的`

如果state保存的是对象,不能直接对对象进行修改,而是拷贝一份进行修改

可以使用immer库简化

:::tip DEEP DIVE immer原理
由 Immer 提供的 draft 是一种特殊类型的对象,被称为 Proxy,它会记录你用它所进行的操作。这就是你能够随心所欲地直接修改对象的原因所在!从原理上说,Immer 会弄清楚 draft 对象的哪些部分被改变了,并会依照你的修改创建出一个全新的对象。
:::

:::tip :rocket: 为什么在 React 中不推荐直接修改 state?
* 调试:如果你使用 console.log 并且不直接修改 state,你之前日志中的 state 的值就不会被新的 state 变化所影响。这样你就可以清楚地看到两次渲染之间 state 的值发生了什么变化
* 优化:React 常见的 优化策略 依赖于如果之前的 props 或者 state 的值和下一次相同就跳过渲染。如果你从未直接修改 state ,那么你就可以很快看到 state 是否发生了变化。如果 prevObj === obj,那么你就可以肯定这个对象内部并没有发生改变。
新功能:我们正在构建的 React 的新功能依赖于 state 被 像快照一样看待 的理念。如果你直接修改 state 的历史版本,可能会影响你使用这些新功能。
* 需求变更:有些应用功能在不出现任何修改的情况下会更容易实现,比如实现撤销/恢复、展示修改历史,或是允许用户把表单重置成某个之前的值。这是因为你可以把 state 之前的拷贝保存到内存中,并适时对其进行再次使用。如果一开始就用了直接修改 state 的方式,那么后面要实现这样的功能就会变得非常困难。
* 更简单的实现:React 并不依赖于 mutation ,所以你不需要对对象进行任何特殊操作。它不需要像很多“响应式”的解决方案一样去劫持对象的属性、总是用代理把对象包裹起来,或者在初始化时做其他工作。这也是为什么 React 允许你把任何对象存放在 state 中——不管对象有多大——而不会造成有任何额外的性能或正确性问题的原因。
:::

0 comments on commit 6c5d544

Please sign in to comment.