Jie's blog

A man running on IT Road


  • 首页

  • 关于

  • 标签

  • 归档

http-cache

发表于 2018-03-15

http cache

http 协议与浏览器的缓存机制

(简易)浏览器执行步骤:

浏览器 -> 发请求 -> 响应请求 -> 解析资源 -> 显示

如果每次对于页面的静态资源都跑以上步骤,会影响页面加载渲染速度,使得用户体验被大打折扣。

(缓存优化)浏览器执行步骤:

浏览器 -> 发请求 -> 从缓存读取 -> 解析资源 -> 显示

直接到缓存读取比起从发请求到服务器响应省去了网络传输等途径,加快了浏览器进入解析的进度。

在前端茫茫的优化道路上,对于静态资源(html/js/css/img/webfont)的请求上有几个理想的指标:

  • 页面能够以最快的速度获取到其所有的静态资源
  • 当服务器静态资源未更新时,再次访问不请求服务器,从而是读取浏览器缓存
  • 当服务器静态资源更新时,请求服务器是最新静态资源。

其实无非就是该请求的时候快速响应下载,不该请求的时候读取缓存资源

http 头部信息

http header 包含了缓存相关的规则信息

强制/指定 缓存(Expires/Cache-Control)

指资源缓存的方式及有效期

Expires

是 http 1.0 的产物,在 http 1.x 后使用 Cache-Control 代替,http 1.x 是没实现 Cache-Control。
Expires 的值为服务端返回的到期时间,在响应 http 请求时告诉浏览器在过期时间前浏览器获取静态资源的方式是从浏览器缓存取数据,而无需再次请求。
Expires 返回的是服务端的时间,与之比较的是客户端本地设置的时间,有可能存在导致差错。

Cache-Control

定义请求缓存指示

  • public:响应被缓存,并且在多用户间共享。
  • private:默认值,响应只能够作为私有的缓存,不能再用户间共享;
  • no-cache:响应不会被缓存,而是实时向服务器端请求资源。
  • max-age:数值,单位是秒,从请求时间开始到过期时间之间的秒数。基于请求时间(Date 字段)的相对时间间隔,而不是绝对过期时间;

Cache-Control 中设定的时间会覆盖 Expires 中指定的时间。若 Cache-Control 的值为ax-age=0,则立即过期重新请求。

Pragma

该字段是兼容 http 1.0 没有 Cache-Control 出现的。

  • no-cache,只有唯一一个选项,效果作用和Cache-Control:no-cache一样

对比/条件 缓存

第一次请求的时,服务端会响应缓存的标识和数据回到客户端,下次请求时,客户端会被该标识带到服务器,服务器根据缓存标识进行判断,若成功返回 304,则客户端使用缓存资源

HTTP 304: Not Modified
标准解释:Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供 If-Modified-Since 头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

Last-Modified/If-Modified-Since

Last-Modified:
在响应请求的头部里,用于告知浏览器资源的最后修改时间

If-Modified-Since:
再次请求时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。服务器通过此If-Modified-Since与被请求资源的修改时间mtime进行对比。
mtime 大于 If-Modified-Since,响应资源,状态码 200。
mtime 小于或等于 If-Modified-Since,则资源无修改,返回状态码 304 通知浏览器读取对应的 cache 资源。

mtime,此处是举例说明,该字段用的是 node file.stats 类的属性

example

首次请求时,请求头部没带If-Modified-Since,需要获取资源修改时间,并响应Last-Modified字段。

1
2
ctx.set('Last-Modified', stats.mtime.toUTCString());
ctx.body = fs.createReadStream(rootPath);

再次请求时,浏览器发出的请求会带上If-Modified-Since字段,服务器获取资源修改时间与带上来的If-Modified-Since进行对比。

1
2
3
4
5
6
7
8
9
10
if (
ctx.get('if-modified-since') &&
ctx.get('if-modified-since') === stats.mtime.toUTCString()
) {
ctx.status = 304;
return;
} else {
ctx.set('Last-Modified', stats.mtime.toUTCString());
ctx.body = fs.createReadStream(rootPath);
}

Etag/If-None-Match

Etag:
服务对资源的唯一标示

If-None-Match:
再次请求时,通过此字段通知服务器上次请求时,服务器返回的资源的唯一标示。服务器通过此If-None-Match与被请求资源在服务器上资源的标示进行对比。匹配成功,响应资源,状态码 200。匹配不成功,则资源无修改,返回状态码 304 通知浏览器读取对应的 cache 资源。

优先级高于 Last-Modified/If-Modified-Since,比其处理更加细腻精准,Etag 主要为了解决 Last-Modified 无法解决的一些问题。

eg:

  • 如资源在短时间内被修改多次,但由于Last-Modified对比只精确到秒级别,故请求依旧走缓存,从而没法获取最新的资源。
  • 资源定期生成,但内容不变,Last-Modified切被改变,从而引发请求会获取资源,而不是去走缓存。

example

首次请求时,请求头部没带If-None-Match,需要获取资源然后根据 file 内容生产 hash,并响应Etag字段。

1
2
3
4
5
6
7
8
9
10
11
function getHash(str: any) {
const chash = crypto.createHash('sha1');
return chash.update(str).digest('base64');
}

const file = await fs.readFile(rootPath);
const hash = getHash(file);
...
ctx.set('Etag', hash);
ctx.body = fs.createReadStream(rootPath);
...

再次请求时,浏览器发出的请求会带上If-None-Match字段,服务器获取资源计算 hash 与带上来的If-None-Match的 hash 值进行对比。

1
2
3
4
if (ctx.get('if-none-match') && ctx.get('if-none-match') === hash) {
ctx.status = 304;
return;
}

项目 demo 地址,yarn run httpCache

备注:开发时注意关闭浏览器在打开开发者工具时关闭缓存的设置

end

http 协议的缓存机制是 web 静态资源缓存及优化的另一种方式,再进一步优化,前端可以采取preloading(预加载)、service work(浏览器新功能)等方式加快页面解析速度。

前端状态管理

发表于 2018-03-08

前端状态管理

Diff

redux/vuex: action -> store -> view
mobx:stroe -> view

  • 层面上 redux 和 vuex 都是基于 Flux 思路的产品。
  • 底层上 redux 遵循不可变的数据原则,重新生成 state。vuex 和 mobx 则是 state 依赖是基于运行时,允许直接修改 state,其中 vuex 是通过 getter/setter 实现的双向绑定自动更新对应的视图层上。而 mobx 的 state 是采取可观察的方式,使用装饰器来绑定对应的 state 和视图层。
  • redux 和 vuex 上,从 action -> store 的中间还有一层 reducer 或 mutation 去更新 state 从而改变 stroe -> view,不同于 mobx 的只专注 stroe -> view。在改变视图层上 mobx 成本是最低最快的,然而 redux 和 vuex 在 action 层面上可以做更多的限制或者业务逻辑从而规范代码和安全性方面,mobx 虽然专注点不一样,但其同样也可以在useStrict使用起 action 层,不过这要使用者自己去规范实施。

mobile

发表于 2018-03-04

移动端适配

自从手淘双十一 Flexible 移动适配方案的出现,一度成为众多开发者的首选,也衍生出各种方案,但原理都一样。再到如今的 vw 方案的出现,更加完美的适配移动端。

这边文章不会重复讲关于 Flexible 和 VW 如何实施,请阅读 👇 链接,本文章持续记录开发遇到问题

Flexible

相关链接

  • 使用 Flexible 实现手淘 H5 页面的终端适配_双 11 前端技术连载, Layout, mobile 教程_w3cplus

VW

相关链接

  • 分享手淘过年项目中采用到的前端技术_mobile 教程_w3cplus
  • 如何在 Vue 项目中使用 vw 实现移动端适配_vw, Layout, 布局, Vue, mobile 教程_w3cplus
  • 再聊移动端页面的适配_Layout, 布局, mobile, CSS 教程_w3cplus

webSafe-csrf

发表于 2018-03-02

web safe csrf

在 web 应用上存在很多安全风险,如 csrf(XSRF),既伪造用户请求向网站发起恶意请求,是一种对网站的恶意利用。

通常防御方式都是服务器分发凭证,每次提交请求或者表单时带上凭证给服务器校验是否为恶意的请求

常用的防范方案

  • 对于服务端渲染的表单页面可以把 token 渲染及隐藏在特定的地方,from 表单提交的时候把该 token 通过约定的参数带给后台
  • 将 token 设置在 Cookie 中,在提交 post 请求的时候提交 Cookie,并通过 header 或者 body 带上 Cookie 中的 token,服务端进行对比校验
  • 将 token 存在 custom header 上,服务端通过校验请求自定义头部字段值
  • 在链接 url 上带上 token 参数

Example

基于 koa 框架采用服务端 setCookie 的方式来讲解

步骤

  • 服务器接受到请求,通过响应页面时将 token 渲染到页面上的 form 隐藏域中<input type="hidden" name="_csrf" value="xxxx">
  • 服务端将 token 设置 cookie 带回客户端response headers -> Set-Cookie: xxx
  • 当用户发送 GET 或者 POST 请求时带上_csrf参数(对于 Form 表单直接提交即可,因为会自动把当前表单内所有的 input 提交给后台,包括_csrf)
  • 后台在接受到请求后解析请求的 cookie 从 session 中 获取 secret 的值,然后和用户请求提交的 _csrf 做解析比较,如果相等表示请求是合法的

所需 middleware

koa-session

option

  • key,cookie key,默认 koa:sess
  • maxAge,存储时间,默认 1 小时
  • overwrite,覆盖同名的 cookie,默认允许
  • httpOnly,仅服务器可以访问 cookie,不予许客户端 js 访问,默认开启
  • signed,安全性相关,koa 的 cookie 本身带了安全机制的签名app.keys 密钥,通过ctx.cookies.set('name', 'tobi', { signed: true })就可以设置,对于 cookie 默认开启签名
  • rolling,强制在每个响应中设置会话标识符 cookie。 过期被重置为原始的 maxAge,重置到期倒计时。默认关闭。
  • renew,会话即将过期时更新会话。默认关闭
  • store,外部存储
  • encode,自定义编码
  • decode,自定义解码

koa-session 默认的 session 存储方式 cookie,同时也支持外部存储默认配置下,会使用 cookie 来存储 session 信息,也就是实现了一个”cookie session”。这种方式对服务端是比较轻松的,不需要额外记录任何 session 信息,但是也有不少限制,比如大小的限制以及安全性上的顾虑。用 cookie 保存时,实现上非常简单,就是对 session(包括过期时间)序列化后做一个简单的 base64 编码。其结果类似

koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjozMDM0MDg1MTQ4OTcwfSwiX2V4cGlyZSI6MTUxNzI3NDE0MTI5MiwiX21heEFnZSI6ODY0MDAwMDB9;

在实际项目中,会话相关信息往往需要再服务端持久化,因此一般都会使用外部存储来记录 session 信息。外部存储可以是任何的存储系统,可以是内存数据结构,也可以是本地的文件,也可以是远程的数据库。但是这不意味着我们不需要 cookie 了,由于 http 协议的无状态特性,我们依然需要通过 cookie 来获取 session 的标识(这里叫 externalKey )。koa-session 里的 external key 默认是一个时间戳加上一个随机串,因此 cookie 的内容类似

koa:sess=1517188075739-wnRru1LrIv0UFDODDKo8trbmFubnVmMU

koa-csrf

原理:

koa-csrf 会在 session 中保存一个secret字段,创建一个新的密钥。

1
ctx.session.secret = ** (Create a new secret key synchronously)

使用密钥生成 token,由于每次请求都是重新生成 salt,因此每次 token 都不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# secret是上面生成的密钥
# salt是salt是随机生成的字符串,长度可自定
ctx.csrf = token = salt + '-' + hash(salt + '-' + secret)

# hash函数源码
function hash (str) {
return crypto
.createHash('sha1')
.update(str, 'ascii')
.digest('base64')
.replace(PLUS_GLOBAL_REGEXP, '-')
.replace(SLASH_GLOBAL_REGEXP, '_')
.replace(EQUAL_GLOBAL_REGEXP, '')
}

验证的时候,只需要取出 token 头部的 salt,再从 session 中取出 secret,再生成 expected,与 token 对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 校验函数
function verify (secret, token) {
if (!secret || typeof secret !== 'string') {
return false
}

if (!token || typeof token !== 'string') {
return false
}

var index = token.indexOf('-')

if (index === -1) {
return false
}

var salt = token.substr(0, index)
var expected = this._tokenize(secret, salt) # _tokenize函数为生成token

return compare(token, expected) # 判断是否一致
}

添加中间件

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
...
# 添加session会发机制中间件
this.app.use(
session(
{
key: config.cookie, // cookie key
maxAge: 86400000, // 存储时间,默认1小时
overwrite: true, // 覆盖同名的cookie
httpOnly: true, // 仅服务器可以访问cookie,不予许客户端js访问
signed: true, // 安全性,签名
rolling: false,
renew: false
},
this.app
)
);
# 添加CSRF中间件
this.app.use(
new CSRF({
invalidSessionSecretMessage: 'Invalid session secret',
invalidSessionSecretStatusCode: 403,
invalidTokenMessage: 'Invalid CSRF token',
invalidTokenStatusCode: 403,
excludedMethods: ['GET', 'HEAD', 'OPTIONS'],
disableQuery: false
})
);
...

创建 ejs 模板,其中_csrf认证的字段

1
2
3
4
5
6
<form action="/csrf/register" method="POST">
<input type="hidden" name="_csrf" value="<%= csrf %>" />
<input type="email" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<button type="submit">Register</button>
</form>

ctx.csrf带有生成的 token

1
2
3
4
5
6
7
8
async index(ctx: any) {
if (ctx.method === 'GET') {
await ctx.render('csrf', {
title: 'web safe csrf',
csrf: ctx.csrf
});
}
}

第一次访问的时候服务器会响应 setCookie,后面请求都会

提交时候把_csrf字段带给服务器校验

项目 demo 地址,yarn run csrf

相关链接

koa-session 基础知识(写的还是不错)
聊聊 CSRF(另一种实现方式)

json-web-token

发表于 2018-02-24

json-web-token

json-web-token,简称jwt。是服务端开发的认证的一种方式,是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体/载荷(payload)和签名(signature)。

header中的内容指定的是jwt的签名算法

1
2
3
4
{
"typ": "JWT", // 类型
"alg": "HS256" // 算法,默认HS256算法。
}

payload这一部分为实体的状态,每个claim包含传输的信息,以及用于服务验证的信息。即里面可以存我们想要自定义验证的信息(如我们可以用户的id等需要使用的数据),可以包含规范定义的claim的信息。

1
2
3
4
5
6
7
{
"iss": "MRLYJ",
"iat": 1416794645,
"exp": 1448338978,
"aud": "gopeak.cn",
"sub": "jwt",
}
  • iss: jwt的发行者(可选)
  • sub: 主题(可选)
  • aud: jwt的接收者(可选)
  • jti: 令牌的唯一标识符(可选)
  • exp: 过期时间,numericData类型,Unix时间戳(可选)
  • iat: 签发时间,numericData类型,Unix时间戳(可选)
  • nbf: 有效起始时间(当前时间在nbf里的时间之前,则Token不被接受),numericData类型,Unix时间戳(可选)

signature签名

生产token具体步骤

1
2
3
4
5
6
7
8
9
10
11
# 先加头部信息的JSON对象进行[base64编码]
header = encodeBase64(header)
# 再把载体信息的JSON对象进行[base64编码]
payload = encodeBase64(payload)
# 需要提供一个secret(密钥),进行签名操作
key = 'secretkey'
signature = HMAC-SHA256(key, encodeBase64(header) +
'.' + encodeBase64(payload))
# 最后生成的令牌
token = encodeBase64(header) + '.' +
encodeBase64(payload) + '.' + encodeBase64(signature)

jwt传输到服务端的方式相对传统方式比较灵活的,实际场景中,我们接受到服务端认证后返回的token,可以通过cookie或者http请求中自定义头部信息。在通常情况下,大部分会选择头部方法,由于cookie很容易受安全和跨域的影响。

头部规范

1
Authorization: Bearer eyJhbGci...

基于node服务端的实现

所需

  • koa2 框架
  • koa-jwt 基于koa框架的jwt中间件
  • jsonwebtoken 用于生产token令牌和验证token有效性

koa-jwt

用来校验权限和解析token用的

jwt(obj1).unless(obj2)

  • obj1
  • secret,密钥
  • passthrough
  • key,附属在ctx.state上的属性,存放解析后的数据
  • obj2
  • path,数组类型,设置忽略校验的路径

jsonwebtoken

jsonwebtoken是基于node实现jwt功能的一个库

jwt.sign(payload, secretOrPrivateKey, [options, callback])

  • payload: 载体,类型为object、buffer、string,对于使用exp字段时必须是object。
  • secretOrPrivateKey: 密钥。类型可以查看官方github文档
  • option
  • algorithm (default: HS256)
  • expiresIn: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, “2 days”, “10h”, “7d”
  • notBefore: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, “2 days”, “10h”, “7d”
  • audience
  • issuer
  • jwtid
  • subject
  • noTimestamp
  • header
  • keyid

其中expiresIn, notBefore, audience, subject, issuer这些属性也可以设置在payload(object), 在其分别对应的
exp,nbf,aud,sub,iss。

更多的api解释

开发

思路上实现验证的流程大体上几个步骤

  • 在应用上配置koa-jwt中间件
  • 当权限失效的时候,设置拦截的中间件
  • 实现注册
  • 实现登录

定义权限验证失败中间件

当请求经过koa-jwt校验时,如果没有token或者token已经失效了,该中间件会给出对应的状态码为401的错误信息,如果我们没自定义中间件来处理改错误时会直接返回给用户。

1
2
3
4
5
6
7
8
9
10
this.app.use((ctx, next) => {
return next().catch((err) => {
if (401 === err.status) {
ctx.status = 401;
ctx.body = 'Protected resource, use Authorization header to get access\n';
} else {
throw err;
}
});
});

koa-jwt

1
2
3
4
5
6
this.app.use(jwt({
secret: config.secret,
}).unless({
path: ['/jwt/register', '/jwt/login']
})
);

其中secret为密钥,不限字符串,文件也行。unless()用于设置那些路径不用经过校验,也就是public api(通常对于注册和登录接口是不需要校验权限的)。当校验成功时,koa-jwt会在请求的上下文,即ctx的state中添加user属性,该属性的值为解析后的数据,当然你也可以更换键名。

1
2
app.use(jwt({ secret: 'shared-secret', key: 'jwtdata' }));
# 可以通过ctx.state.jwtdata获取值

register
实现用户注册,将用户信息存入数据库。实际项目中需要加密,字段校验等措施。

login
实现用户登录,查询数据库是否该用户,然后将有关数据通过jsonwebtoken签名生产token并返回给客户端,客户端可以通过localstorage等方式将token存储在本地存储,在每次的 HTTP 请求中,都将 token 添加在 HTTP Header Authorazition: Bearer token 中。后端每次经过koa-jwt去验证该token的正确与否。只有token正确后才能访问到对应的资源。

1
2
3
4
5
jsonwebtoken.sign({
data: user._id,
}, secret, {
expiresIn: 60 * 2, // 设置token有效期
})

secret为密钥,记住这里的secret必须要与中间件jwt()中的secret 一致。

users
实现获取用户信息,我们需要在头部自定义Authorization字段,就可以获取对应的资源。

1
Authorization: Bearer ...(token)

项目地址,运行npm run jwt

node-debug

发表于 2018-02-08

Debug

node的调试方式

打日志

通过使用console.log方法,此方式相当便捷,当相对于难点的bug还是比较无力。

node自带的debugger调试器

官方文档debugger

基于TCP的协议,通过commod界面可以对nodejs脚本进行调试

1
2
3
4
# 旧版的node
node debug **.js
# 最新的node
node inspector **.js

运行后终端出现commod界面

调试

  • cont, c - 继续执行
  • next, n - 下一步
  • step, s - 跳进函数
  • out, o - 跳出函数
  • pause - 暂停运行代码(类似开发者工具中的暂停按钮

设置断点

  • 可以通过文本打添加debugger
  • setBreakpoint(), sb() - 在当前行设置断点
  • setBreakpoint(line), sb(line) - 在指定行设置断点
  • setBreakpoint(‘fn()’), sb(…) - 在函数体的第一条语句设置断点
  • setBreakpoint(‘script.js’, 1), sb(…) - 在 script.js 的第 1 行设置断点
  • clearBreakpoint(‘script.js’, 1), cb(…) - 清除 script.js 第 1 行的断点

执行

  • run - 运行脚本(调试器开始时自动运行)
  • restart - 重新启动脚本(每次设置完断点后可以运行此命令方便点)
  • kill - 终止脚本

node inspect和node --inspect的区别?
node inspect启动是command界面操作debug方式,而node --inspect可以上 Chrome 的开发者工具附加到 Node.js 实例以用于调试和性能分析,这是由 V8 的检查器集成来的。

中规中矩,还是在编辑器上设置断点调试才是最方便的,不过多学中node debug方式有益无害。

node-inspector

这是社区提供的优秀的debugging工具。
Node Inspector 是 Node.js 应用程序的调试器接口,通过它可以使用 Blink(Chrome 浏览器内核)开发工具来进行 debugging。

1
2
3
4
# 安装
npm install -g node-inspector
# 启动
node-debug **.js

node-debug 命令将在默认浏览器中加载 Node Inspector。

node inspector仅使用于chrome和opera,若是其它浏览器需要在打开inspect页面

node自带新的方式

官方文档command inspect参数选项

V8检查器集成允许Chrome DevTools和IDE等工具调试和配置Node.js实例。 这些工具通过tcp端口附加到Node.js实例,并使用Chrome Debugging Protocol调试协议进行通信。

可以是用此方式代替node-inspector工具的使用。

1
node --inspect **.js

与--inspect-brk对比
--inspect-brk会在应用代码的第一行断开,方便从头开始调试

要求:

  • Node.js 6.3+
  • Chrome 55+
  • Enable a new way of Node.js debugging in Chrome
  • 在chrome输入chrome://flags/#enable-devtools-experiments
  • 开启Developer Tools experiments
  • 重启Chrome
  • 打开Chrome的DevTools->Setting->Experiments 选项(在重启之后它开始可见)
  • 按6次’SHIFT’以显示隐藏的实验功能
  • 选中 “Node debugging”
  • 打开/关闭 DevTools

最新版的chrome已经没有Node debugging,默认启动

use

运行node --inspect **.js
终端输出

打开chrome,输入chrome://inspect/,如下选择你对应的inspect

调试

IDE

使用IDE工具上自带的debug功能进行调试。

推荐使用vscode

也可以使用其它的编辑器webstorm等进行debug。

2017 End

发表于 2017-12-31

2017 我的总结

2017,这一年下来,或喜或悲,有得也有失。

live

为什么先讲生活呢,我觉得每个人在自己漫长的职业生涯中,都需要有向上的生活感,正确的价值观。前阵子网上曝出一条新闻某某因被辞职而跳楼身亡,事因我也不想在此博客写,一笔带过吧!人一旦轻易的放弃留下来的莫过于他人的悲痛。生活处处都存在太多的无奈,不易,以及危机,又有多少人能够游刃有余的去面对呢,因此我们都有自己不能为之而放弃的理由。

2017,这一年我成为了小叔子,舅舅。打上的了各种大人才有的标签,哦,不,我也是一名大人了!
Alt text

2017,这一年,妈妈的唠叨还是一样的多,父母也在辛苦的一件一件的完成他们的所想的事,我也不能怠慢自己的人生。

子在川上曰:逝者如斯夫,不舍昼夜。

work

2017,这一年换了一份工作,离开的迪奥大家庭,这家公司算是我职业生涯的一个转折点吧,感谢给予我帮助的人。

2017-04-17,驻场到了南航,认识了很多小伙伴,也在这里辛苦的历练。

发现这一年自己有一个很好的提升,就是面对工作上的问题处事态度上有提升,较之前更为稳重。一个人的成熟,并不表现在获得了多少成就上,而是面对那些厌恶的人和事,不迎合也不抵触,只淡然一笑对之。当内心可以容纳很多自己不喜欢的事物时,这就做气场,叫做成长。

plan

在2016年末的时候的计划是2017年多读点书,然后且没实现。

2018,至少读4~n本书。
2018,坚持每月写一篇技术博客。
2018,继续提高自己当前行业技术水平。
2018,学习python,学习以外的技术,不能让时代淘汰。
2018,多交友多去做些生活上自己喜欢的事。
2018,想多闯荡下自己职业以外的行业。

end

2017年底,我也在这里说下为什么我的网名叫来不及上高峰。
在小时候,总有些事遇不上,比如当地一个较好中学在刚好自己要上中学时,却停止招生…那时,老妈还在抱怨我这遇不上,那遇不上的,那时的我也是一副吊儿郎当的样子不以为然的样子,直到后面慢慢长大,慢慢地去感受生活,慢慢的接触到社会,才明白不是自己遇不上,而是自己没去把握,没去努力。故在大学时候打算换个网名,就叫了来不及上高峰。其实没什么来不及的,最差的结果,不过是大器晚成罢了。

为什么我们会习惯这种重复:指定目标、忽略目标、放弃目标,年复一年。一旦这变成了一种习惯,理想的丰满和现实的骨感会越来越明显。更可怕的是你习惯而不自知。

blog

发表于 2017-12-31

blog

本人 blog 现在以这个为主。

会把之前的搬到这里来。

react-native实践

发表于 2017-10-15

react-native

记录 react native 开发过程的种姿势和 bug 收集

欢迎反馈和 bug 提供

项目地址,官方环境开发体验

tools

  • react-native-cli,官方推出创建项目的工具(自带 Watchman 依赖,不行另行安装)。
  • create-react-native-app,是由 Facebook 和 Expo 联合开发用于快速创建项目的工具,特点无需用户再按装 adroid sdk 和 xcode。需要搭配 Expo 开发的工具 xde 客户端使用。
  • Node。
  • Yarn,包管理器,如同 Npm。
  • Nuclide,集成集成环境,可用于编写,运行和测试应用。
  • Watchman,监视文件系统变更工具。

开发搭配

  • 官方:react-native-cli(创建项目),Yarn(包管理器),Watchman(文件变更工具),flow,Node(环境)。
  • expo:create-react-native-app(创建项目),Expo XDE(命令行工具与发布工具,同时支持使用内部模拟器),Expo(手机预览 app),Exp(node,编译,可选择)。
  • 开发 and 打包工具:ios(Xcode,simulation),android(andorid-sdk,android-ndk,java,geymotion)。
  • 编辑器:vscode(推荐),vscode-react-native-tool(集成插件)。

vscode

  • vscode-react-native-tool,提供了一个开发环境,调试,运行,react-native 相关操作命令。
  • path Intellisens 文件路径提示补全。
  • Auto Close Tag 自动闭合标签。
  • Auto Rename Tag 自动重命名标签。
  • react-native-react-redux-snippets-for-es6-es7 代码片段。
  • vscode-language-babel 语法高亮。
  • vscode-react-native-tool 的 salsa 代码提示功能未能启用(估计操作姿势有问题),使用 typescript,typings 代替使用。
1
2
3
4
5
6
7
8
npm install typescript@next -g
// 先全局安装typescript,如何修改vscode配置,手动指定路径。
// --> "typescript.tsdk":typescirpt安装的路径
npm install typings --global
// 全局安装typings, 智能提示功能
typings install dt~react-native --save
// 在项目的根目录运行
// end 重启

debug

run ios

  • 预先编译要对其开发的平台,如 ios 就需要 xcode 上编译并在设备或者模拟器运行。后续开发不需要再次编译,可以直接类似开发运行开发,除了对 ios 相关有代码功能才需要编译。
  • 使用 vscode reactnative-tools 插件 run ios 运行模拟器上,前提电脑需要安装 xcode。

debugger

  • npm start 运行下在模拟器 command + d,选择 Debug JS Remotely 开启 debug 功能,在 Chrome 上 debugger 调试。
  • 使用 vscode reactnative-tools 插件 start packager 运行下,在 vscode debugger 功能选项下添加配置 react native: debug ios 或者 react native: debug android,选择对应的平台,点击进行调试。如何在模拟器 command + d,选择 Debug JS Remotely 开启 debug 功能。(推荐,比较方便直接在代码上打断点)。

redux tools

使用 redux-devTools 工具调试 redux。
app 开发和 web 开发在使用 redux-devTools 工具上是不一样,需要远程支持的。安装remote-edux-devTools,remotedev-server,建议在 package.json 上添加“remotedev –hostname=localhost –port=5678”次命令,方便快速启动,另外代码添加

1
2
3
4
5
6
7
8
9
10
11
...
import { composeWithDevTools } from 'remote-redux-devtools';
...

...
const composeEnhancers = composeWithDevTools({ realtime: true, port: 5678, hostname: 'localhost' });
...

...
createStore(reducers,initialState, composeEnhancers());
...

其中代码中的 5678 端口必须和添加 script 命令中的端口配置一致。启动 redux-devTools 的 remote 功能,把端口等配置输入即可。

components

  • react-navigation
  • react-native-scrollable-tab-view
  • react-native-vector-icons
  • react-native-splash-screen

bug

  1. react-native 0.47.1,使用 redux-saga,需要修改 babel-preset-react-native 文件/config/main.js 中’babel-preset-transform-regenerator’,建议注释掉。

react-skills

发表于 2017-05-01

React Skills

react使用技巧和最佳实践
本文会根据react动向持续更新!react 16.0.0版本Fiber算法,性能将大大提升!

Tool

因为jsx语法浏览器上不支持,所以使用react技术栈开发都需要借助babel,及它的扩展插件来进行转码编译。react搭配的开发构建工具建议使用为webpack。

  • babel-loader:webpack的loader插件
  • babel-preset-es2015:es5语法
  • babel-preset-react:react语法
  • babel-preset-stage-0es7语法提案,已经有多个版本

CSS

CSS的规范用法在前端发展的领域中不断的演变,从CSS模块,Css in JavaScript中可以看出CSS发展,尤其是React对前端HTML,JS,CSS三者一起混合使用。一个组件包含着结构、样式和逻辑,完全违背了”关注点分离”的原则,使得很多人都不适应,早期传统倡导的是html,css,js分离,到最后组件化思路,从另一个角度上思考,react的做法有利于组件的隔离。每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,更加放便复用。

但目前Css in JavaScript仍然没有更好的解决的方案。目前最好的方式是一个组件一个css文件,再把其引入。

1
import './styles/**.css'

components

before

在react 15.5.0的版本createClass api将出现废弃警告,并提供create-react-class单独的模块,使用方法和createClass一样。16.0.0版本即将废弃!

1
2
3
4
5
6
var React = require("react")
var Jie = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});

after

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*class*/

import React, {Component} from 'react'
class Jie extends Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

/*function 无状态,对于展示类的组件,使用这类写法最好不过了*/

const Greeting = (props) => {
return <div>{props.name}</div>
};

propTypes 和 defaultProps

propTypes 和 defaultProps 是静态的属性

在15.5.0 react中propTypes api被独立成了一个新的包 prop-types;

before es5

1
2
3
4
5
6
7
8
var Greeting = React.createClass({
propTypes: {},
getDefaultProps: function() {
return {
name: 'Mary'
};
},
});

before es6

1
2
3
4
5
/*function和es6 class的静态属性或方法的定义只能单独在name后*/

class JieContainer extends Component {}
JieContainer.propTypes={};
JieContainer.defaultProps={};

after es7

1
2
3
4
class JieContainer extends Component {
static propTypes = {}
static defaultProps = {}
}

state,setState

state

  • es6的构造方法constructor中this关键字则代表实例对象,constructor方法默认返回实例对象(this,所以也可以指定对象返回),constructor指向类本身。
  • es6类中的方法都是定义在类的prototype属性上,如同构造函数的prototype属性。
  • es6继承,子类是没有自己的this对象,而是继承父类的this对象,然后对其进行加工,故需要在constructor中调用super方法。es5使用构造器方法来继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this)),es6的继承实质是先调用super创造父类的实例对象this,再用子类的构造函数修改this。

before es5

1
2
3
4
5
var Counter = React.createClass({
getInitialState: function() {
return {count: this.props.initialCount};
}
});

before es6

1
2
3
4
5
6
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
}

after es7

1
2
3
class Counter extends React.Component {
state = { expanded: false }
}

setState

  • setState在调用的过程中不会立即改变state中的值
  • 更新state过程来引发重新绘制
  • 同时多次调头setState函数所产生的效果会被合并
1
2
3
4
5
6
7
// state.count 当前为 0  

this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});

// state.count 现在是 1,而不是 3

setState 支持2个参数,1:{} 或者 function(preState,props),2:function 更新成功后的回调函数

1
2
3
4
5
/*上面的解决方法*/

this.setState((prevState) => ({
count: prevState.count + 1
}));

function this

为了确保每个方法被调用的时都绑定正确的this,以及避免渲染时重复绑定,在es6时候我们通常这么写。

1
2
3
4
5
6
7
8
class Counter extends React.Component {
constructor(props) {
super(props);
this.toString = this.toString.bind(this);
}

toString(){}
}

after
由于箭头函数没有自己this,toString方法中调用this是来自类的实例

1
2
3
4
5
6
7
8
9

class Counter extends React.Component {
constructor(props) {
super(props);
this.toString = this.toString.bind(this);
}

toString = ()=>{}
}

Destructuring(解构 state,props)

before

1
2
3
4
5
6
7
8
9
10
11
const name = this.state.name;

//or

const namer = this.state.name;

function MyComponent(props) {
return (
<div>{props.name}</div>
)
}

after

1
2
3
4
5
6
7
8
9
10
11
const {name} = this.state;

//or

const {name:namer} = this.state;

function MyComponent({name}) {
return (
<div>{name}</div>
)
}

JSX 三元运算符

当出现大量的判读时候,三元运算符可读性会显得极差

1
2
3
4
5
<div>
{
props.bool ? ** : ** ? ** : ** ? ...
}
</div>

抛开一些依赖可以采取另一种解决方式IIFE,虽然会有点性能牺牲,但代码的维护性和可读行就相对提高很多。

1
2
3
4
5
6
7
8
9
10
11
<div>
{
(()=>{
if(){
return ...
}else if(){
return ...
}else ...
})()
}
</div>

若只是判读渲染某个元素的话

1
2
3
4
5
6
7
8
9
10
11
12
{
isTrue &&
<p>True!</p>
}

//而非

{
isTrue
? <p>True!</p>
: <none/>
}

Arrow Funciton

箭头函数没有this,当=>后面没有{},则相当于return

1
2
3
const fun = () => {}

const fun = ({name}) => (<div>{name}</div>) === const fun = ({name}) => {return (<div>{name}</div>) }

Extended operator

扩展运算符,切勿把大量数据使用扩展运算符传递,因为会造成非常昂贵的计算,应该按需传递

1
2
3
4
5
const fun = (props) => (<MyComponent {...props} />)

===

const fun = ({name,sex}) => (<MyComponent name={name} sex={sex} />)

闭包

向子组件传递function时候尽量避免直接写函数,因为每次渲染时候都会创建函数并传递到子组件,造成性能浪费。

1
2
3
4
5
6
7
8
9
10
11
12
...

handleChange = ()=>{}

render(){
return(
<ChildComponent
onChang = {()=>{}} // xx
onChange = {this.handleChange}
/>
)
}
12
Jie

Jie

来不及上高峰的个人博客

11 日志
7 标签
GitHub ZhiHu
© 2018 Jie
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.3