组件通信

爷孙组件通信

爷孙组件通信主要有 3 种方式:

  1. 将孙子组件的 props 封装在一个固定字段中
  2. 通过 children 透传
  3. 通过 context 传递

假设有个三层组件,爷爷分别给儿子和孙子发红包

先看青铜解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return <Daddy money={someMoneyForDaddy} moneyForSon={someMoneyForMe} />;
}
function Daddy(props: { money: number; moneyForSon: number }) {
const { money, moneyForSon } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
<Me money={moneyForSon} />
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

Daddy 组件会透传爷爷给孙子的组件给 Me。这种方案的缺点很明显,以后爷爷要给 Daddy 和 Me 发糖果的时候,Daddy 还得加字段。

将孙子组件的 props 封装在一个固定字段中

按照 1 的方案,我们可以固定给 Daddy 添加一个 sonProps 的字段,然后将 Grandpa 需要传给孙子的状态全部通过 sonProps 传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return <Daddy money={someMoneyForDaddy} sonProps={{money: someMoneyForMe}} />;
}
function Daddy(props: { money: number; sonProps: Parameters<typeof Me>[0]; }) {
const { money, sonProps } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
<Me {...sonProps}/>
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

这样以后要给 Me 加字段,就不用改 Daddy 了。但要测试 Daddy 时还得 mock Me 组件的数据,Daddy 和 Son 耦合。

通过 children 透传

children 类似于 vue 中的 slot,可以完成一些嵌套组件通信的功能

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
function Grandpa() {
const [someMoneyForMe] = useState(100);
const [someMoneyForDaddy] = useState(101);
return (
<Daddy money={someMoneyForDaddy}>
<Me money={someMoneyForMe} />
</Daddy>
);
}
function Daddy(props: { money: number; children?: React.ChildNode }) {
const { money, children } = props;
return (
<div className="daddy">
<h2>This is Daddy, received ${money}</h2>
{children}
</div>
);
}
function Me(props: { money: number }) {
const { money } = props;
return (
<div className="son">
<h3>This is Me, received ${money}</h3>
</div>
);
}

将 Daddy 的嵌套部分用 children 替代后,解耦了子组件和孙子组件的依赖关系,Daddy 组件更加独立。

作为替代,也可以传递一个组件实例:

三种方案的决策

  1. 第一种方案一般用于固定结构和跨组件有互相依赖的场景,多见于 UI 框架中的复合组件与原子组件的设计中
  2. 第二种常用在嵌套层级不深的业务代码中,比如表单场景。优点是顶层 Grandpa 的业务收敛度很高,一眼能看清 UI 结构及状态绑定关系,相当于拍平了 React 组件树
  3. 第三种比较通用,适合复杂嵌套透传场景。缺点是范式代码较多,且会造成 react dev tools 层级过多;Context 无法在父组件看出依赖关系,必须到子组件文件中才能知道数据来源

React DevTools

General 设置
image-20220222024702149

General 面板中最重要的功能就是 **”Highlight updates when components render”**。勾选上之后,可以查看 React 重绘时,页面哪些部分有更新。在遇到性能问题时,可以快速帮助决策在哪部分不需要重绘的组件部分添加 React.memo 阻止重绘。

Debugging 设置
image-20220222024832097

“Append components stacks to console warnings and errors.”

可以帮助我们定位 React 报错信息来自哪个组件

阅读全文 »

本篇笔记的目的:站在更高的角度看待React

React的设计思路

  • 解决UI编程的痛点

    1. 状态更新,UI不会自动更新,需要手动调dom api进行更新

      1. 欠缺基本代码的封装和隔离,代码层面没有组件化
      2. UI之间数据依赖,需要手动维护,如果依赖链路过长,会有 callback hell的问题
  • 响应式编程

image-20220210203412954

  • 期望能

    1. 状态更新,UI自动更新
    2. 前端代码组件化,可复用,可封装
    3. 状态之间的依赖关系,只需要声明即可
    
  • 组件化

    1. 组件是组件的组合/原子组件
    2. 组件内拥有状态,外部不可见
    3. 父组件可将状态传入组件内部
    
  • 状态归属问题

    1. react是单向数据流,只能父组件将状态传递给子组件(但可以将改变父组件状态的方法传递给子组件实现父子组件通信)
    2. 如何解决不合理的状态上升问题?
    3. 组件状态改变后,如何更新dom
    
阅读全文 »

HTTP 有哪些常见状态码

2xx(成功状态码)

200 OK

请求成功,服务器已成功处理了客户端的请求,并返回了请求的内容。

3xx(重定向状态码)

301 Moved Permanently

被请求的资源已永久移动到新位置,服务器返回这个状态码时,会在响应头的 Location 字段给出资源的新 URL。搜索引擎会更新其索引中的链接。

应用场景:如果一个网站更改了页面的 URL 结构,希望将旧的 URL 永久重定向到新的 URL

302 Found

表示请求的资源临时移动到了新位置。与 301 不同的是,搜索引擎不会更新索引中的链接

304 Not Modified

说明无需再次传输请求的内容,也就是说可以使用缓存的内容

阅读全文 »

HTTP缓存机制

HTTP缓存过程

第一次发送请求 - 服务器响应,响应标头带上Cache-Control、Expires、ETag、Last-Modified
第二次发送请求 - 先根据Cache-Control、Expires判断强缓存是否失效,如果失效,If-None-Match中的上一次ETag和If-Modified-Since中的上一次Last-Modified来判断是否变更,如果没有变更,返回304让浏览器使用本地缓存

image-20210808204737157

强缓存

Cache-Control

Cache-Control 是 HTTP/1.1 中新增的属性,在请求头和响应头中都可以使用,常用的属性值如有:

  • max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效
  • no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜
  • no-store:禁止使用缓存(包括协商缓存),每次都向服务器请求最新的资源
  • private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应
  • public:响应可以被中间代理、CDN 等缓存
  • must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证

Expires

Expires 的值是一个 HTTP 日期,在浏览器发起请求时,会根据系统时间和 Expires 的值进行比较,如果系统时间超过了 Expires 的值,缓存失效。

由于和系统时间进行比较,所以当系统时间和服务器时间不一致的时候,会有缓存有效期不准的问题。Expires 的优先级在三个 Header 属性中是最低的。

协商缓存

当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。

ETag/If-None-Match

ETag/If-None-Match 的值是一串 hash 码,代表的是一个资源的标识符,当服务端的文件变化的时候,它的 hash码会随之改变

Last-Modified/If-Modified-Since

Last-Modified/If-Modified-Since 的值代表的是文件的最后修改时间

如何实现ETag?

Nginx中ETag是如何实现的: 上次修改时间 + content-length

缓存策略

题目:简述你们前端项目中资源的缓存配置策略
题目:现代前端应用应如何配置 HTTP 缓存机制
关于 http 缓存配置的最佳实践为以下两条:

  • 文件路径中带有 hash 值:一年的强缓存。因为该文件的内容发生变化时,会生成一个带有新的 hash 值的 URL。前端将会发起一个新的 URL 的请求。配置响应头 Cache-Control: public,max-age=31536000,immutable
  • 文件路径中不带有 hash 值:协商缓存。大部分为 public 下文件。配置响应头 Cache-Control: no-cacheetag/last-modified

但是当处理永久缓存时,切记不可打包为一个大的 bundle.js,此时一行业务代码的改变,将导致整个项目的永久缓存失效,此时需要按代码更新频率分为多个 chunk 进行打包,可细粒度控制缓存。

HTTP缓存策略

  1. webpack-runtime: 应用中的 webpack 的版本比较稳定,分离出来,保证长久的永久缓存
  2. react/react-dom: react 的版本更新频次也较低
  3. vendor: 常用的第三方模块打包在一起,如 lodash,classnames 基本上每个页面都会引用到,但是它们的更新频率会更高一些。另外对低频次使用的第三方模块不要打进来
  4. pageA: A 页面,当 A 页面的组件发生变更后,它的缓存将会失效
  5. pageB: B 页面
  6. echarts: 不常用且过大的第三方模块单独打包
  7. mathjax: 不常用且过大的第三方模块单独打包
  8. jspdf: 不常用且过大的第三方模块单独打包

为什么需要加密?

因为http的内容是明文传输的,明文数据会经过中间代理服务器、路由器、wifi热点、通信服务运营商等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是“中间人攻击”。

用对称加密可行吗?

对称加密最大的问题就是密钥的传输,而密钥的传输又需要加密(反复套娃),故而单纯的对称加密是无法解决问题的。

阅读全文 »

引言:

感觉promise的思想非常有意思,为了代码的可读性和可维护性提供了一种新的异步编程方法,接触了一段时间的工程代码也让我体会到代码可维护,可拓展的必要性。

为什么要用Promise?

对于异步操作,例如文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var fs = require('fs')

fs.readFile('./a.txt', 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(data);
})
fs.readFile('./b.txt', 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(data);
})
fs.readFile('./c.txt', 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(data);
})
阅读全文 »

SPA的理解

  1. 单页Web应用(single page web application,SPA)。

  2. 整个应用只有一个完整的页面

  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。

  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

路由

后端路由
  • 注册路由: router.get(path, function(req, res))

  • 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由
  • 注册路由: <Route path="/test" component={Test}>

  • 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

阅读全文 »

实现一个new

new的具体步骤

  1. 内存中创建一个新对象
  2. 新建对象的 _ proto _指向构造函数的prototype
  3. 调用构造函数,函数的this指向新创建的对象
  4. 执行构造函数内部的代码(给新对象添加属性)
  5. 如果构造函数返回非空对象,则返回该对象,否则,返回新创建的对象
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
function Person(age, name) {
this.age = age;
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(this.name);
}

function myNew(fn, ...args) {
// 修改obj.__proto__ = fn.prototype,如果直接obj = {}会出现没有对象标识的问题(对象标识为Object),还会使得方法没有被创建
const obj = Object.create(fn.prototype) //fn.prototype代表 用当前对象的原型去创建
//现在obj就代表Dog了,但是参数和this指向没有修改
const rel = fn.apply(obj,args)
/*
正常规定,如何fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
因为比如下面这个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
return {
a: 1,
b: 2,
}
}
const p = new Person('h', 'y');
这里new会优先为构造函数的返回值
所以p的值为{a: 1,b: 2}
*/
return rel instanceof Object ? rel : obj
}
const p1 = myNew(Person, 19, 'hyz');
阅读全文 »
0%