ZONO's World

welcome to my world

记第一次开源经历

根据学习进度,我总觉得自己要真正参与开源至少得等到明年,但没想到在今年快要结束的关口,参与了一次开源,虽然只是一次很小的贡献,但对我来说是一个很好的开头。

经历记录

再学习react时,逐渐感觉学习技术还是直接看文档比较好,毕竟学习时,不可能把知识点完全记住,第一次学习时,更多的是对知识点有个印象,如果看视频的话,遇见问题时,还得重新看视频,而文档则可以直接搜索,找到对应的知识点。

学习react-router时,感觉中文文档写的不尽人意,了解后发现,原来是社区翻译,而主要翻译者时间有限,不能面面俱到,所以就想着能不能自己翻译一下。由此开始了第一次开源经历。

过程还是很简单的,fork、clone、修改、push、pull request。

一些感想

学习者在开始学习技术时,可以从开源者的角度出发。

而热爱开源者,也可以从翻译开始自己的开源入门。

在这个大模型时代,翻译是一件很简单的事。许多技术第一手资料往往是英文的,我们可以将这些资料翻译成中文,这样既可以提高自己的英文水平,也可以进行学习(能一个一个字去理解文档)。

理想的文档翻译我觉得应该是这样的,第一个人靠机翻简单翻译,这样他学了一次并开启了翻译项目,后来的人阅读时,会把难理解的地方对照原始文档又翻译一次,也促进了自己的思考。(相当于做笔记了)

有些困,车轱辘话有些多,但意思如此,与君共勉。

2023年度总结

前言

最近的日子感觉时间过的飞快,还没怎么准备好就期末了。

眼看着这过不了几日便跨年了,各大平台推出了年终报告。而我因为一直专注于准备找工作的事,很久很久没有总结了。

不时常把自己梳理一次,总是会感觉自己忘记了什么。

这里简单的写写23年的总结吧

原本准备在21岁写一篇名叫”送给自己的21岁礼物”的文章,结果拖拖拉拉生日都过了近一个月了都还没写完(而且是在提前准备了近一个月的情况下,也是有够拖的)。

不过随着经历和思考,感觉自己反而简单了许多,这里便和年度总结一起写吧。

(推到22岁写也不错)

回顾

自上大学后,感觉每一年都有不一样的收获。回顾一年,最有里程碑意义的事件有两项:解构自己确认未来十年的主要发展方向。由此呢,我觉得今年的主题应该是“锋藏”(对自己评价感觉怪怪的)。

接下来就细细讲讲关于这一年的细节吧。

主线

解构自己

感觉自己就是那种,纯靠CPU的生物,脑子里总是思考,却总是记不住事。所以我已经忘了是从什么时候开始找到名叫人生的意义的答案的,只是找到答案后的后果蛮多的,最显著的一点就是对于写日记的执着没有了。过去、现在和未来的细细碎碎,似乎变得十分不重要,而那些长久的、可以长时间维持的东西进入了自己的世界,姑且说,自己也变成了一个长期主义者吧。

加上正值大三,是到了面临选择考研还是工作的时候了,于是我尝试做了一件”很麻烦的事”,解构

不知是什么契机,可能是因为飞书的功能太适合解构自己,也可能是程序员思想,想着把自己变成一个产品或者对象,把自己解耦后更好管理,总之我花了不少时间把自己的人生分成了一个个区块。然后把过去放进去,把未来新建文件夹。

这样做的好处,是我未成意料的,后来我创建知识库、设置滴答清单、创建blog的目录,无一不是复用这套解构逻辑。

很庆幸,我可能是做出了关于个人的第一个可以复用的组件(这是从技术上总结的哲学🤔)

如此就确定了自己,滴答清单+blog(因为飞书不支持导出md,于是处于半废弃状态)+一木记账的个人管理系统。

思想转变

自从读了哲学后,感觉自己的认知逐渐开始改变,颇有一种玄幻世界吃上品灵丹的感觉,刚吃下去入口即化,感觉自己不一样了,随着时间迁移药力还在慢慢的扩散。

胡言乱语:有时会思考,再这个人工智能飞速发展的时代,人类也应该不断进化,这个契机就是哲学。

这一年里,我更加关注于自我的发现。举个例子,可能以前,我会因为在出去玩时学习被人说,该休息时就应该全力休息,而陷入自我烦恼(可能这就是i内向人独特的烦恼吧),如今我就会想,我就是这样的人。(这只是例子,玩的时候,谁会学习呀🤣)

还有些细细碎碎的领悟,比如发现大部分时候人类的恐惧都是未知,而人与人之间的隔阂,也是基于互相之间的不了解。所以我尝试把朋友圈开放了,虽然还是没什么人看,估计也是平时懒得经营的人际关系的原因。比如说,奉献者会比互利者和获取者发展的更好···

不过思想这玩意,常常时来时去,而且还有点宗教色彩,所以这里便不细说了。(记录自己一直在思考就行)

关于社交

这一年接触了很多e外向人朋友,可能enfj外向的一种类型着实有着感染力,感觉自己已经变成半e半i的人了。很有意思的是,一部分朋友认为我是e人,另一部分认为我是i人。经过观察认为我外向的人,一般是i人,认为我内向的人一般是e人。

这么说,我是不是可以当一个e/i检测器。

兴趣

渐渐的发现,兴趣这种东西是和知识一样是可以习得的。

而这一年,因为选择了前端这一条道路,相辅相成的我也培养起了编程的兴趣,不过这部分总结还是交给后面的技术一节吧。

至于摄影绘画啥的,可能因为专注点原因,这一年有所侧重,部分计划也未能完成。

摄影

这一年我卖掉了大一买的M43相机,买入了单反。(有意思的是M43还让我赚了两百)再价格不变的情况下,舍弃了视频能力,专注于拍照。

过去总是有点器材党的意思,一直嫌弃M43的画质,如今画质上来了,到时没什么接口了。于是这个暑假,看了不少网课,目前能拍出一些能看的照片了。(照片墙企划中)

绘画

没什么提升🤔,原本准备今年至少完成一幅作品,看样子得顺延了。

一直再积累、领悟着美。之前有一篇文章谈论过。

技术

其实11月前,我还打算考研,考研的数学资料都已经准备好了(说起来我现在都还没转手,有意dd)。后来为了跟家长说自己要去找工作,史无前例的用了两个多小时告诉他们我为什么这样决定。不过还好,交流完信息之后,他们也支持了我的决定。

但为什么决定去找工作,最重要的还是要与自己交流,这里写过一篇小文章,准备以后完善成具体的方法论写一次随笔。此处便简单讲讲。

选择走开发的路,终究是机缘巧合,最大的契机其实应该和上文的兴趣链接起来。原本我是一个适应性极强的人(有点自夸,加随遇而安也行),因为参加了某个开发的项目,再开发中,发现竟然进入了以往绘画、摄影才会出现的心流状态。

这可能也是之前枯坐画画培养出来的,但无论如何我进入了这个状态,并且从11月决定要找工作,开始系统准备到现在,我都处于一个过往十分期待能进入的求道状态(感觉没做出成就之前说这些都莫名其妙的)。

反正,我想这应该就是我的路了(虽然感觉如果我去考研,也会这样认为)。

其实,这点还蛮好理解的,再某个方向,发现能展示自己的价值,那证明这条路就是可以走的。

如此两大里程碑都聊到了

其他

一如既往的,这一年认识了许多新朋友,很高兴与你们相遇。(平时,不用社交软件聊天,单纯懒得闲聊,或者不会闲聊,是的不怎么会闲聊😫)

展望

回到关键词“锋藏”,可以遇见接下来的一两年里,都是不断积累,这让我感觉有点像武侠小说里面的养🔪术,待到术成时,一剑可封喉。(中二感满满)

回归正题接下来说说明年的一些计划吧。

生活

  • 没什么好说的第一要务便是找到一份好的前端工作。

  • 当有收入时一定要说服家人来北京玩。

  • 如果可以找一个女朋友。(这是顺延多少年的计划了😅)

  • 认识更多的朋友,去遇见,去经历。

  • 细节看滴答清单

兴趣

  • 继续完善对美的定义,还是时间问题,有时间就去看看书、看看课

  • 开启旅行、电影、阅读、写作等计划(找到实习后、或工作后)。

  • 摄影上:开始人像摄影,并开始设计一套摄影计划。还有进行一些有意思的尝试。

  • 绘画上:顺延上一年计划。学呀❗❗

  • 细节看滴答清单

其实还想学乐器啥的😫

技术上

按滴答清单上完成。

  • 开始开源贡献

还有一个近期计划,活过考试周🤡

寄语

总觉得还应该说些什么。

明天大四的同学就要考研了,祝愿他们能给青春一个美好的答卷。也告诉自己,但行好事。莫问前程。

守护心中的辛梅尔

很久没有遇见像《葬送的芙莉莲》这样基调的影视作品了。上一部可能是《朝花夕誓》?但单纯的将两者类比,又太不恰当。因为芙莉莲给我的感觉远不止于芙莉莲的慵懒。

过去,我读过很多很多修仙小说,主角从凡人一路除魔卫道,寿命也蹭蹭蹭的上涨,小时总觉得这是很有道理的事,一路打怪升级,最后打败最大的boss,成为天下第一,这是很正常的事。但随着年龄的增长,我开始读到更多的书,它们有的说,天下第一有什么意思,我要重生,去挽回那个最想最爱的人;有的又说,天下第一隐去了功名,去世间逍遥快活去了……

这样看来看去,人们的追求一直没有确切的定式,何为成功,何为意义,不同的人给了不同的解释。


过往的影视作品中,似乎少有认真描述、或者准确表达寿命长的人的性格。

这大概是过去的时代里,人们的认知很难感知到,除了自己生活的小圈子外,这个巨大的星球上还在不断发生这各种各样的事。更加无法体会到从四维角度,将时间概念引入这个世界的超巨大的信息量。

这些总总,带给人们的最终就是无尽的渺小感,或者说,虚无感。

世界似乎给我们每人一个上万块碎片的白色拼图,又时不时拿那些拼成不同形状,又涂上不同颜色的作品说,这就是成功。


那到底什么是意义呢?又有什么能定义生活的意义呢?很遗憾这道题本来就无解。

不过,但对于你个人来说,是有解的。

芙莉莲漫长的生命中,得到的答案是,辛梅尔给她的去收集一切有意思的魔法。这是爱情?或者单纯是兴趣,其实都无妨。总之,是有了这样的一个意义诞生了。至于更深的意义,那就去问辛梅尔吧。


人类的历史还很短,但已经足够了,或者说,当生活于这个世界时,就足够你去得到一个人生的意义了。

它可能来自于父母、朋友,也可能来自于历史、传说。总之很多很多,足够忙活一辈子了。

然后,就是不必去细究这些意义的意义,我们没必要傲慢到认为一辈子相信这个意义的人当了一辈子的傻子,也没必要傲慢的认为,未来的哲学家们无法找到意义的答案。

这个答案,很像宗教不是吗?意义本身就是一种信仰吧。


不知道作者再讲述这段异世界故事时,是如何想的,可能也是讲述这对抗虚无的同时,发现爱意的落脚,于是虚无中长出了蓝色的花。

最后,致我继续在人间凑数的日子,继续去搜集那些无聊,但是愉快的魔法吧。

for infor of区别

js的遍历真的让人迷迷糊糊的,单纯的for、while循环不够,map、forEach还能理解,居然还多几个for…in,for…of。着实让人摸不到头脑

相信大家在学习遍历时,一定碰见了两个名词,enumerableiterator

要区别它们首先得理解这两者的差异。

enumerableiterator

enumerable

对象的属性对象,让我们打印出来看看

1
2
3
4
5
6
7
8
9
10
11
var obj = { p: "a" };
let prop = Object.getOwnPropertyDescriptor(obj, "p"); //查看属性p的属性对象
console.log(prop);
// {
// value: a,
// writable: true,
// enumerable: true,
// configurable: true,
// get: undefined,
// set: undefined
// }

回顾:之前关于属性的三种状态Extensible(不可加)、seal()(不可加减)、freeze()(不可加减改)

iterator

区别

其实更多的要区分的是for inObject.keys

for in可以遍历所有可枚举项,包括原型中的内容

浅显的理解,我们可以把

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

const array1 = ["a", "b", "c"];
//for in
for (const val in array1) {
console.log(val);
}//打印索引
//0
//1
//2

//for of
for (const val of array1) {
console.log(val);
}//打印值
//a
//b
//c

由上文可知对象不可迭代但可以被枚举

1
2
3
4
5
6
7
8
9
10

for (let i in obj) {
console.log(i);
console.log(obj[i]);
}
for (let i of obj) {
console.log(i);
console.log(obj[i]);
} //err:obj is not iterable
//效果可以自己打印一下

对象的维度

属性

继承

方法

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
  • //pass

以后完善

该文会不断更新

button和input的一些迷惑之处

让我们敲下面几行代码

1
2
3
<input type="button" value="点击" />
<input type="submit" value="点击" />
<button >点击</button>

有意思的是运行后,呈现的效果完全相同。

form提交应该使用input:submit还是button:submit? - 知乎 (zhihu.com)

  • <button> 元素是一个更灵活的按钮,可以自定义外观和样式(可以包含文本、图像或其他 HTML 元素),并且可以在点击时执行 JavaScript 操作。如果它位于表单中且没有指定 type 属性,则默认为 “submit” 类型,可以提交表单。
  • <input type="button"> 是一个用于执行 JavaScript 操作的按钮,不会自动提交包含它的表单。
  • <input type="submit"> 是一个用于提交表单的按钮,当用户点击它时,将触发表单的提交动作。
  • 旧版本的浏览器可能对 <button> 元素的行为和样式支持不一致。但<button> 元素通常被认为是更语义化的选择

明明getElementsByClassNamequerySelectorAll可以实现一样的功能为什么要弄两个

getElementsByClassName 是一个原生的 DOM 方法,快一些,返回的是一个动态的 HTMLCollection 对象,该对象会随着 DOM 的变化而实时更新。。

querySelectorAll 是在 DOM API 中引入的新方法,返回的是一个静态的 NodeList 对象

  1. 使用 getElementsByClassName 的应用场景:
    • 动态更新的元素集合:如果需要选择的元素集合需要实时更新,例如在用户交互或事件处理程序中动态添加或删除元素时(比如写todolist
    • 仅按类名选择元素:如果您只需要按类名选择元素,并且不需要使用其他选择器(如标签名、ID、属性等),getElementsByClassName 是一个更简洁的选择。
  2. 使用 querySelectorAll 的应用场景:
    • 复杂的选择器需求:如果您需要使用更复杂的选择器,例如使用层级选择器、伪类选择器等来选择元素,querySelectorAll 提供了更灵活的选择器语法,使您能够使用更多种类的选择器。
    • 静态节点列表:如果您需要获取一个静态的节点列表,并且不需要实时更新,querySelectorAll 可以提供更适合的功能。
    • 使用其他选择器:如果您需要按照标签名、ID、属性等其他选择器选择元素,querySelectorAll 提供了更广泛的选择器选项。

如何区分NodelistHtmlCollection

一个例子(可以直接复制后运行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul>
<li class="example"></li>
<li class="example"></li>
<li class="example"></li>
</ul>
</body>
<script>
// 使用querySelectorAll获取NodeList和getElementsByClassName获取HTMLCollection
const nodeList = document.querySelectorAll("li");
const htmlCollection = document.getElementsByClassName("example");

// 访问节点数量
console.log("NodeList length:", nodeList);
console.log("HTMLCollection length:", htmlCollection);
// 动态添加元素
const ul = document.querySelector("ul");
const li = document.createElement("li");
li.textContent = "New Element";
li.className = "example";
ul.appendChild(li);

// 验证差异
console.log("Updated NodeList length:", nodeList.length); // 不会更新,3相当于快照
console.log("Updated HTMLCollection length:", htmlCollection.length); // 实时更新,4
</script>
</html>

这里我就想到,react和vue的状态管理,react的状态管理是静态的,看官网的描述就知道,它提到了快照,vue了解不多,但它是基于MVVM的有双向绑定的特征

setPrototypeOf 与 Object.create区别

这里又涉及到了动静问题,如果你之前理解了它们之间的区别也会很好理解
功能不同:

setPrototypeOf 方法用于直接修改一个对象的原型。它接受两个参数,第一个参数是要修改原型的对象,第二个参数是新的原型对象。这会改变对象的原型链,并且会立即生效。
Object.create 方法用于创建一个新对象,该新对象的原型被设置为传入的参数。它接受一个参数,即新对象的原型对象。它不会直接修改现有对象的原型链,而是创建一个新的对象,并将其原型设置为指定的原型对象。
兼容性差异:

setPrototypeOf 方法是在 ECMAScript 2015 (ES6) 中引入的,因此在一些较旧的浏览器或环境中可能不受支持。
Object.create 方法在较新的浏览器和环境中得到广泛支持,包括现代的主流浏览器和 Node.js。
使用方式不同:

setPrototypeOf 方法需要直接操作现有的对象,通过传递新的原型对象来修改对象的原型。例如:Object.setPrototypeOf(obj, newPrototype)。
Object.create 方法是一个静态方法,用于创建一个新对象并设置其原型。例如:Object.create(proto)。

动态组件(类组件的使用)

复习ES6Moudle导入知识:

import React from "react"import {Component,PureComponent}...

import {Component,PureComponent}...不是对React对象进行解构赋值。

创建类

创建一个,并继承React.Component/PureComponent,基于render返回JSX视图

创建一个构造函数(类)的过程:

  • 继承React.Component/PureComponent这个类,一般使用ES6中的class创建类(因为方便)

  • 给当前类设置一个render的方法(放在其原型上):在render方法中,返回需要渲染的视图

第一步、继承

1
2
3
4
5
6
7
8
9
10
//ES5写法(寄生组合继承,可不看)
function AAA() {
React.Component.call(this);//cell继承React.Component
this.state = {
a: 1
}
}
Object.setPrototypeOf(AAA.prototype, React.Component.prototype);//原型继承
AAA.prototype.sum = function () {
}//原型上的方法
1
2
3
4
//ES6写法
class AAA extends React.Componet{
}
exprot default AAA;

第二步、设置Render方法

于是完整的写法:

1
2
3
4
5
6
7
8
9
import React from "react";
class ClassComponent extends React.Component {
render() {
return <div>
我是类组件
</div>;
}
};
export default ClassComponent;

类组件的动态化

继承知识

类组件是动态组件

  • 具备属性及规则校验
  • 具备状态,修改状态可以控制视图更新
    • setState
    • forceUpdate
  • 具备周期函数
    • 严格模式下,一些不安全的周期函数是禁止使用的

PureComponent /pjʊə(r)/

一个小测试:新状态值和原始状态值是相同的堆内存地址「数据发生改变」,我们可以试试继承React.Component和React.PureComponent的区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default class Demo extends React.Component {
state = {
arr: [10, 20, 30]
};
handler = () => {
this.state.arr.push(40);
this.setState({ arr: this.state.arr });
};
render() {
return <div className="demo">
{this.state.arr.join('+')}
<button onClick={this.handler}>更改</button>
</div>;
}
};

自动增加shouldComponentUpdate周期函数,并对原始属性/状态和最新属性/状态进行浅比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const shallowEqual = function shallowEqual(objA, objB){
if (Object.is(objA, objB)) return true;
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Reflect.ownKeys(objA),
keysB = Reflect.ownKeys(objB);
if (keysA.length !== keysB.length) return false;
for (let i = 0; i < keysA.length; i++) {
let key = keysA[i];
if (
!objB.hasOwnProperty(key) ||
!Object.is(objA[key], objB[key])
) {
return false;
}
}
return true;
};

// 测试使用
export default class Demo extends React.Component {
...
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
...
};

非受控组件

不受状态管控的组件「可以基于ref获取DOM元素进行操作」

  • ref=’xxx’ 不推荐使用
  • ref={x=>this.xxx=x}
  • React.createRef

赋值给标签,目的是获取DOM元素;
赋值给类组件,目的是获取组件的实例;
函数组件不能直接设置ref,但是可以基于React.forwardRef /ˈfɔːwəd/实现ref转发!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 被调用的子组件
import React from "react";
const Demo3 = function Demo3(props, ref) {
return <div className="demo3"
ref={ref}>
</div>;
};
export default React.forwardRef(Demo3);

// 父组件
export default class Demo extends React.Component {
componentDidMount() {
console.log(this.box); //-> div.demo3
}
render() {
return <div className="demo">
<Demo3 ref={x => this.box = x} />
</div>;
}
};

关于setState的进阶研究

React18中,对于setState的操作,采用了 批处理

  • 构建了队列机制
  • 统一更新,提高视图更新的性能
  • 处理流程更加稳健

在React 18之前,我们只在 React合成事件/周期函数期间批量更新;默认情况下,React中不会对 promise、setTimeout、原生事件处理(native event handlers)或其它React默认不进行批处理的事件进行批处理操作!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from "react";
export default class Demo extends React.Component {
state = {
x: 10,
y: 5,
z: 0
};
handler = () => {
let { x, y, z } = this.state;
this.setState({ x: x + 1 });
this.setState({ y: y + 1 });
console.log(this.state);
setTimeout(() => {
this.setState({ z: z + 1 });
console.log(this.state);
});
};
render() {
console.log('render');
let { x, y, z } = this.state;
return <div className="demo">
{x}-{y}-{z}
<br />
<button onClick={this.handler}>处理</button>
</div>;
}
};

setState接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default class Demo extends React.Component {
state = {
num: 0
};
componentDidMount() {
for (let i = 0; i < 100; i++) {
/* this.setState({ num: this.state.num + 1 });
console.log(this.state.num); */
this.setState(prevState => {
return {
num: prevState.num + 1
};
});
}
}
render() {
console.log('render');
let { num } = this.state;
return <div className="demo">
{num}
</div>;
}
};

基于flushSync可以清空队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from "react";
import { flushSync } from 'react-dom';
export default class Demo extends React.Component {
state = {
x: 10,
y: 5,
z: 0
};
handler = () => {
let { x, y, z } = this.state;
this.setState({ x: x + 1 });
flushSync(() => {
this.setState({ y: y + 1 });
});
console.log(this.state); // x:11 y:6 z:0 -> render

this.setState({ z: z + 1 });
console.log(this.state);
};
render() {
console.log('render');
let { x, y, z } = this.state;
return <div className="demo">
{x}-{y}-{z}
<br />
<button onClick={this.handler}>处理</button>
</div>;
}
};

静态组件

函数组件是静态组件如何理解?
让我们先来试试这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src\components\Vote.jsx
const Vote = function Vote(props) {
let { title } = props;
let supNum = 10,
oppNum = 5;

return <div className="vote-box">
<div className="header">
<h2 className="title">{title}</h2>
<span>{supNum + oppNum}人</span>
</div>
<div className="main">
<p>支持人数:{supNum}人</p>
<p>反对人数:{oppNum}人</p>
</div>
<div className="footer">
<button onClick={() => {
supNum++;
console.log(supNum);
}}>支持</button>

<button onClick={() => {//合成事件中后序学习
oppNum++;
console.log(oppNum);
}}>反对</button>
</div>
</div>;
};
export default Vote;

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//src\index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import Vote from './component/Vote';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<Vote title="React其实还是很好学的!" />
</>
);

setTimeout(() => {
root.render(
<>
<Vote title="我是五秒后传递的标题" />
</>
);
}, 5000);

可以见到,视图是渲染出来了,但点击按钮只能后台打印,与我们所设计的点击按钮后还渲染视图不符。

静态组件的特点

让我们先看看函数组件渲染的过程:

  1. 第一次渲染组件,把函数执行
  • 产生一个私有的上下文:EC(V)(随便取的名字指大函数)
  • 把解析出来的props(含children)传递进来(会被冻结)。
  • 对函数返回的JSX元素(virtualDOM)进行渲染
  1. 当我们点击按钮的时候,会把被绑定函数执行:
  • 修改上级上下文EC(V)中的变量
  • 私有变量值发生了改变
  • 但是“视图不会更新”
  • 也就是,函数组件第一次渲染完毕后,组件中的内容,不会根据组件内的某些操作,再进行更新,所以称它为静态组件
  • 除非在父组件中,重新调用这个函数组件(可以传递不同的属性信息)

总结一下:

  • 不具备状态、生命周期函数、ref等内容(后续会讲到)
  • 第一次渲染完毕,除非父组件控制其重新渲染,否则内容不会再更新
  • 优势:渲染速度快
  • 弊端:静态组件,无法实现组件动态更新

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
const FunctionComponent = function FunctionComponent() {
let num = 0;
return <div>
{num}
<br />
<button onClick={() => {
num++;
console.log(num); //变量值累加,但是组件不会重新渲染
}}>增加</button>
</div>;
};
export default FunctionComponent;

解决办法

大部分需求,都需要在第一次渲染完毕后,基于组件内部的某些操作,让组件可以更新,以此呈现出不同的效果:
动态组件
方法:类组件、Hooks组件(在函数组件中,使用Hooks函数)

敬请期待——

这一章是对组件化的实战,可以跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//\src\components\dialog.jsx

import PropTypes from 'prop-types';
import React from 'react';

const Dialog = function Dialog(props) {
// 获取传递的属性和插槽信息
let { title, content, children } = props;
children = React.Children.toArray(children);

return <div className="dialog-box" style={{ width: 300 }}>
<div className="header" style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<h2 className="title">{title}</h2>
<span>X</span>
</div>
<div className="main">
{content}
</div>
{children.length > 0 ?
<div className="footer">
{children}
</div> :
null
}
</div>;
};

/* 属性规则校验 */
Dialog.defaultProps = {
title: '温馨提示'
};
Dialog.propTypes = {
title: PropTypes.string,
content: PropTypes.string.isRequired
};

export default Dialog;

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//.\src\index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import Dialog from './components/dialog';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<Dialog title="友情提示" content="大家出门做好个人防护!" />

<Dialog content="我们一定要好好学React!">
<button>确定</button>
<button>很确定</button>
</Dialog>
</>
);

实战——插槽处理

概念

见下图

![cf04ea2fa3fed761901517454264443](E:\appdata\wechat\msg\WeChat Files\wxid_o6y6wc9we49o22\FileStorage\Temp\cf04ea2fa3fed761901517454264443.jpg)

目的:复用性

我们已经知道了如何把子组件放入父组件,但如果有多个子组件要传入父组件又该如何完成呢?

因为子组件传入父组件是对象,于是我们可以把它转换为数组形式,以此达到目的

多个子组件放入父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// src\views\DemoOne.js
//父组件的写法
import PropTypes from "prop-types";
const DemoOne = function DemoOne(props) {
let { title, x, children } = props;//取出属性
//检测children是否存在,若不存在则赋值为空数组,若存在则检测是否为数组,若不是则转换为数组
if (!children) {
children = [];
} else if (!Array.isArray(children)) {
children = [children];
}
return (
<div className="demo-box">
{children[0]}
<br />

<h2 className="title">{title}</h2>
<span>{x}</span>

<br />
{children[1]}
</div>
);
};

/* 设置属性的校验规则 */
DemoOne.defaultProps = {
x: 0,
};
DemoOne.propTypes = {
title: PropTypes.string.isRequired,
x: PropTypes.number,
};

export default DemoOne;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//调用src\index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import DemoOne from '@/views/DemoOne';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<>
<DemoOne title="REACT好好玩哦" x={10}>
<span>哈哈哈哈</span>
<span>你好</span>
</DemoOne>

<DemoOne title="哇卡卡卡">
<span>嘿嘿嘿</span>
</DemoOne>

<DemoOne title="哈哈哈哈哈" />
</>
);

React.Children 对象

但其实我们并不需要自己写对子组件的校验,React有自己的方法。

可以基于 React.Children 对象中提供的方法,对props.children做处理:count\forEach\map\toArray…

开始时,我们会import React,React其实就是一个封装好的对象。

1
2
3
4
5
6
7
8
//也就是
children = React.Children.toArray(children);
//这里直接替换下面代码就能实现,于是就不放完整代码了
if (!children) {
children = [];
} else if (!Array.isArray(children)) {
children = [children];
}

具名插槽

当我们要求将子组件放到我们需要的位置时,就可以使用具名组件

于是修改后的父组件代码是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// src\views\DemoOne.js    另一个文件没变
import PropTypes from "prop-types";
import React from "react";

const DemoOne = function DemoOne(props) {
let { title, x, children } = props;
children = React.Children.toArray(children);
let headerSlot = [],
footerSlot = [],
defaultSlot = [];
children.forEach((child) => {
// 传递进来的插槽信息,都是编译为virtualDOM后传递进来的「而不是传递的标签」
let { slot } = child.props; //获取插槽的名称
//根据插槽的名称,把当前的插槽信息存放到对应的数组中,slot和对应的值,都可以自定义,要方便调用者。
if (slot === "header") {
headerSlot.push(child);
} else if (slot === "footer") {
footerSlot.push(child);
} else {
defaultSlot.push(child);//一般不起名字的,默认导入
}
});

return (
// 通过数组的形式,把插槽信息渲染到页面中
<div className="demo-box">
{headerSlot}
<br />

<h2 className="title">{title}</h2>
<span>{x}</span>

<br />
{footerSlot}
</div>
);
};
/* 设置属性的校验规则 */
DemoOne.defaultProps = {
x: 0,
};
DemoOne.propTypes = {
title: PropTypes.string.isRequired,
x: PropTypes.number,
};

export default DemoOne;

对应的index.jsx,实验一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//调用src\index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import DemoOne from '@/views/DemoOne';

const root = ReactDOM.createRoot(document.getElementById('root'));

//因为有插槽我们传入chilren组件时,不需要考虑顺序,名字对应就行。
root.render(
<>
<DemoOne title="REACT好好玩哦" x={10}>
<span slot="footer">我是页脚</span>
<span>哈哈哈哈</span>
<span slot="header">我是页眉</span>
</DemoOne>

<DemoOne title="哇卡卡卡">
<span>嘿嘿嘿</span>
</DemoOne>

<DemoOne title="哈哈哈哈哈" />
</>
);

待规整部分

实现具名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// index.jsx
root.render(
<>
<FunctionComponent>
<div className='slot-box' slot="head">
我是插槽信息1
</div>
<div className='slot-box' slot="foot">
我是插槽信息2
</div>
</FunctionComponent>
</>
);
// views/FunctionComponent.jsx
import React from "react";
const FunctionComponent = function FunctionComponent(props) {
let children = React.Children.toArray(props.children),
headSlots = children.filter(item => item.props.slot === 'head'),
footSlots = children.filter(item => item.props.slot === 'foot');
return <div>
{headSlots}
我是组件内容
{footSlots}
</div>;
};
export default FunctionComponent;
0%