GithubHelp home page GithubHelp logo

aaa-blog's People

Stargazers

 avatar  avatar

Watchers

 avatar

aaa-blog's Issues

如何优雅的定制化element-ui样式

施工中:

问题的源头,我们遇到的问题

饿了吗的element-ui作为一个相当成熟的组件库。
基于element-ui来搭建自己的系统时遇到的问题,需要在其基础样式上进行覆盖,使得组件能符合自己团队的样式设计。如何选用合适的方式在element-ui的主题基础上,符合自己产品的UE设计。

让我们尝试一步一步从简单到复杂,由浅入深由实际需求场景出发逐步探讨这个问题。

基于element-ui自定义样式的方式

  1. 利用class name覆写组件样式

实际场景,比如可能我们自己产品设计中table的header的颜色和字体颜色需要重置,
example:

<template>
  <el-table
    class="my-table"
    :data="tableData">
    <el-table-column
      prop="date"
      label="日期"
      width="180">
    </el-table-column>
    <el-table-column
      prop="name"
      label="姓名">
    </el-table-column>
  </el-table>
</template>
<style>
  .my-table.el-table {
    thead th {
      background: #f5f7fa;
    }
    th > .cell {
      color: #333333;
    }
  }
</style>  

注意事项:
这里需要注意的是style不能封闭作用域,并且如果组件样式的覆写不需要作用域全局的话,一定使用一个父class name包裹,这样保证不会污染全局样式定义。

好处:这种方式总体来说是一种最为直接的覆写�方式,而且易于理解。使用chrome的调试工具我们就能方便的定位到要修改样式的dom的className,然后按照设计图覆写样式就可以了。如果样式的覆写只在特殊的场景下,那么这是一种很适合的方式。

局限性:但是对于一些基础性的样式重置,比如border-color,以及一些功能性颜色的设置,这种方式并不能很好的解决问题。如果有大量全局性的定义需要覆写的话,使用这种方式的工作量是比较大。

  1. 通过覆写官方的原始定义变量
    在element-ui是由SCSS编写,源文件中有大量的样式定义,是基于更为基础的变量。
    比如:按钮,特殊文本,进度条的颜色都依赖于�一下几个:
    源代码如下:
/* 以下文件在 */


/* 以下文件在 */

如果在我们的产品设计中,primer,info,warning,danger等基础设计的样式定义。那么我们通过上文中的方法无疑工作量会非常大。比如:如果我们要对几种颜色全部进行统一的覆写定义,那么:

FUCK !!! 需要写这么多代码?

按照官方教程http://element.eleme.io/#/zh-CN/component/custom-theme
这2种方式,可以看做是官方提供了一组基础样式定义接口给我们,通过覆写这些接口,我们能更加方便的定制属于自己的主题。对于上一节中提到的场景,我们只需要修改
todo 解决上面问题的代码

就能重置所有:按钮,进度条,特殊文本的颜色。
注意事项:
这里主要是自己在开发中遇到的一些坑。
TODO 1. 环境搭建,配置webpack使用对应的loader与vue的webpack冲突。
TODO 2. npm安装te跪了,cnpm亚克西。

局限性:即便是覆写这些变量,也有不能实现的功能,比如:
TODO table的header,body的padding不同。并没有足够的接口。我们这时就需要根据自己的需求,在已有的theme scss源代码的基础上进行二次开发。

  1. 魔改theme-chalk主题源代码,在理解element-ui官方theme源码设计与结构的情况下,实现符合自己团队产品的主题。

  2. 深入,理解element-ui的整个css结构,理解设计规范与设计原色

  3. 面向切面编程,css的定义与组件的定义解耦。

  4. BEM设计规范,以及我们应该如何魔改

  5. 这一步让我们尝试将视野再次放大,让我们这次考虑

从设计师设计到样式文件定义,看做一个整体。

  1. 复盘与反思,我们应该编写代码

前端文本省略实现记录

  1. 针对一行出现的情况

overflow:hidden; //超出的文本隐藏

text-overflow:ellipsis; //溢出用省略号显示

white-space:nowrap; //溢出不换行

  1. 针对2行的情况
    css3解决了这个问题,解决方法如下:

display:-webkit-box; //将对象作为弹性伸缩盒子模型显示。

-webkit-box-orient:vertical; //从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)

-webkit-line-clamp:2; //这个属性不是css的规范属性,需要组合上面两个属性,表示显示的行数。

overflow:hidden;

text-overflow:ellipsis;

display:-webkit-box;

-webkit-box-orient:vertical;

-webkit-line-clamp:2;

ts编写的模块, 作为npm包发布, 相关流程与配置

施工中....

现在我们要把写好的模块发布到npm上, 方便其他项目使用, 一些项目构建信息:

  1. 使用语言: typescript
  2. 打包工具: webpack

各个文件配置与注意事项

  1. 配置webpack.config.js
  output: {
    path: path.resolve(__dirname, './'),
    filename: '[name].js',
    libraryTarget: 'umd',
    library: 'OurLibraryName'
  }

这里一定要设置libray和libraryTarget, 不然发布的模块使用的时候, import到的所有对象都将为空
(不要问我是怎么知道的 OTL)

  1. package.json的配置
{
  "name": "the-name-of-our-module",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "prepublish": "npm run build",
    "build": "node_modules/.bin/webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "项目的git地址"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^2.3.2"
  },
  "dependencies": {
  }
}

这里主要注意配置好: main, types, prepublish, repository, 这几项.
main: 表示模块的入口文件, 对应的就是我们在webpack.config.js中配置的输出文件路径, 这个一定要对应上, 不然在其他项目中引入该模块时, 将会报错找不到该模块. (不要问我是怎么知道的 OTL+1).
types: ts的类型文件生成, 这个记住配置开启.
repository: 仓库地址, 这里也可以配置为svn地址, 不过一般都是github, url配置为项目地址就可以了.

准备完毕发布到npm

  1. 自行搞定npm账号
  2. 调用以下命令登陆账号:
  npm login
  1. 发布模块, 该指令会读取我们项目下的package.json并自动发布模块:
  npm publish

执行成功就可以在npm官网上看到我们发布的模块了.

更新或删除我们发布的模块

  1. 更新模块
    当我们修改了模块代码以后想要更新模块, 需要修改package.json中的version字段,
    然后调用: npm publish, 不然想要发布会报错.

  2. 删除发布的模块:

  npm unpublish  name-of-published-lib

实际上并不会删除。

常用脚本

/ 删除目录
"clean": "rimraf dist/*",

// 本地搭建一个 HTTP 服务
"serve": "http-server -p 9090 dist/",

// 打开浏览器
"open:dev": "opener http://localhost:9090",

// 实时刷新
 "livereload": "live-reload --port 9091 dist/",

// 构建 HTML 文件
"build:html": "jade index.jade > dist/index.html",

// 只要 CSS 文件有变动,就重新执行构建
"watch:css": "watch 'npm run build:css' assets/styles/",

// 只要 HTML 文件有变动,就重新执行构建
"watch:html": "watch 'npm run build:html' assets/html",

// 部署到 Amazon S3
"deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/",

// 构建 favicon
"build:favicon": "node scripts/favicon.js",

git各种使用笔记

同事拉了一个分支做开发,并且提交,我如何拉取到本地

  1. 使用 git remote -a 指令查看所有的远程分支
    这个时候能够看到所有的远程分支信息,找到我们需要拉取到本地的分支名

  2. 拉取远程分支并创建本地分支
    使用指令git checkout -b 本地分支名 origin/远程分支名,就可以顺利拉取远程分支到本地了

react decorator 使用

记一次常量折叠优化, bug

  1. BUG成因
    {a:1, b:2, c:3}.c + 1 这种情况可以优化: 3 + 1 => 4, 最终优化成一个常数
    但是针对 [1,2,3].slice(2) 这种情况就比较麻烦了, 如果我们优化为 slice(2),
    那么很显然, 最后的结果是不正确的。

  2. 修复方式

TODO

ts工程环境搭建

这里记录下, 每次都是去翻手册和文档.

开发环境搭建

  1. npm初始化:
cnpm init
  1. 安装ts相关加载和依赖:
cnpm install --save-dev typescript ts-loader tsconfig-paths-webpack-plugin source-map-loader webpack
  1. 在项目根目录下创建tsconfig.json作为tsc的配置
{
  "compilerOptions": {
    "outDir": "./dist/",
    "declaration": true,
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "lib": [ "es2017", "DOM", "es6" ],
    "target": "es5"
  },
  "include": [
    "./src/**/*"
  ]
}
  1. 在项目根目录下创建webpack.config.js
    这里ts文件的loader使用ts-loadeer, 并且使用了路径配置
var webpack = require('webpack');
var path = require("path");
var TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

function resolve(dir){
  return path.join(__dirname, '..', dir);
}

module.exports = {
  entry: {
    app: './src/main.ts',
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, './dist')
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"],
    plugins: [
      new TsConfigPathsPlugin(/* { configFileName, compiler } */)
    ]
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "ts-loader" },
    ]
  }
}

一些依赖的使用

core-js 主要使用一些集合类

# core-js
cnpm install --save core-js
cnpm install --save-dev @types/core-js

# reflect-metadata
cnpm install --save reflect-metadata
cnpm install --save-dev @types/reflect-metadata

一些常用指令

  1. 更新所有依赖包到最新版本
cnpm update -S
cnpm update -D

测试环境搭建

这里使用了jasmine作为测试框架, 以及ts-node和jasmine-ts来帮助执行测试代码:
ts-node 为typescript提供一个运行环境
jasmine-ts 使得jasmine可以使用typescript作为输入文件

cnpm i -D jasmine ts-node jasmine-ts typings  @types/node @types/jasmine
node_modules/.bin/jasmine-ts init

常见错误处理

  1. Error: Cannot find module 'webpack'
    因为在全局安装了webpack, 但是local找不到:
  npm link webpack
  1. awesome-typescript-loader无法正确生成文件
    s-panferov/awesome-typescript-loader#432 (comment)
    暂时这个BUG还没修复, 只能换个loader了

webpack使用

  1. loader选项
  • loader执行顺序从右向左, 例如: require("style-loader!css-loader!sass-loader!./my-styles.sass"); 执行顺序分别为: 读入文件, sass-loader处理, css-loader处理, style-loader处理, 语法上通过!来使用新的loader覆写前面的内容.

  • 可以通过Rule.enforce选项来调整loader的执行顺序, loader按照pre, line, normal, post的顺序排列, 默认值为normal

ps: loader如同是定义了一类资源的编译方式, 似乎一旦在webpack.config.js中module.rules定义了对应的loader, 那么这个loader就没办法取消了, 对于所有满足条件的文件都会经过定义的loader的处理.

代码的抽离与复用

交互评审的一般性套路,如何 argue ?

  1. 检查交互是否是通用的? 是,是否有规范? 否,美誉规范?
  2. 对应的交互是否在使用的组件库中有对应的 feat ?
  • 有,可以直接用
  • 没有需要做一点调整(逻辑上,样式上) ,实现成本小
  • 没有, 需要依靠当前已有的机制来实现,实现成本中
  • 没有, 实现成本很大.需要部分重构,并且对后续维护带来影响
  1. 对于非规范,并且实现成本很大的交互,注意砍掉

程序员修炼之道,正交性

  当任何系统的各组件相互高度依赖时,就不再有局部修正(local fix)这样的事情

当整个图上节点间通过大量的边链接的时候,一个区域的修改必然影响到大面积的子图.

  如果你编写正交的系统,你得到两个主要好处:提高生产率与降低风险

  如果你对正交的组件进行组合,生产效率会有相当微妙的提供.假设某个组件做 M 件事情,另一件做 N 件事情.
  如果它们是正交的,而你把它们组合在一起,结果就能做 M x N 件事情.

通过对系统的正交拆分,获取多个相互无关的多个子图
(垂直拆分(底层服务 + 上层应用),水平拆分(领域,职责拆分))
造成的影响:

  1. 通过合理的分割与抽象,缩小了问题的规模
  2. 使得变化的影响被约束在有限的范围以内
  3. 提高系统的复用率






## 一些书籍

https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201

## 学习文档
https://docs.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer

云风的感悟,感觉和康威定律相合
https://blog.codingnow.com/2020/05/tpp_manuscript.html#more

## 综述类文档
https://cloud.tencent.com/developer/article/1391169

## 美团
https://tech.meituan.com/2018/03/16/meituan-food-delivery-android-architecture-evolution.html

https://tech.meituan.com/2020/05/14/octo-mns-2.0.html

## https://blog.christianposta.com/design/the-cost-of-code-reuse-abuse/

## the-fallacy-of-reuse
https://udidahan.com/2009/06/07/the-fallacy-of-reuse/

提炼观点:
1. 代码的使用可以分为: use 和 reuse
2. 对于工具库: 框架(react),公共工具包,基础服务等是使用.
3. reuse 是一个更领域模型相关的概念,意味着基于已有的代码搭建新的代码,其中更重要的问题是如何把 code 放到合适的地方
4. code reuse 是有代价的, code 的编写者需要理解系统应当做什么,code 的使用者需要花费成倍的时间理解系统应当做什么.
5. 如果代码不存在复用抽离,那么就不会有相互依赖.一旦有了 reuse 代码之间就形成了依赖,更多复用,越多依赖;更多依赖,越多 rebugging.
6. 随着代码的 reuse 越多,依赖的规模也会越加庞大,修改底层代码会导致整个依赖路径上代码的 rebugging.正所谓牵一发而动全身.
7. 为了减少总体的复杂度,我们需要根据领域做微服务拆分

进一步抽象观点:
1. code 并不是孤立存在的,代码之间通过依赖构成有向无环图.
2. 在代码演进的过程中,图上一个代码节点的变更会导致所有后继节点的 rebugging.
3. 代码的复用与演进是有代码的,当代码规模增大的时候,任何一个节点的变更都会导致大面积的子图的 rebugging.
4. 通过将整个图拆分为多个相对独立的子图,能极大的减少代码复用的成本

观点 2 的算法解释:

当系统搭建初期,实体相对较少,实体间的关系比较简单,即图上节点与边的数量比较少,这个时候图上的边数也接近 O(V)
随着


## https://bit.dev/ 貌似很吊



## 界限上下文
https://martinfowler.com/bliki/BoundedContext.html

2020-07-21

合理的抽象,坏的抽象

好的抽象方式 => 好的抽象的思维框架(好与坏的评价标准) 
### 如何判断一个抽象是好的?
用实践来证明:1. 扛得住需求变化 2. 

### 如何获得一个好的抽象?
1. 尽量的理解业务,获取尽量多的信息.信息获得得越全面,越能防止抽象出现局部最优解的情况
2. 根据业务类型,做好技术调研.业内积累的解决方案.
3. 在 1 的基础上,抽象定义好我们面对的问题.数据结构与算法,设计模式.

### 抽象的定义我们的问题
比如从一个 Table 组件,什么是 Table 组件?他的定义是什么?
是一个 Layout 组件,输入顺序数据结构数组,输出是一个以特定排版方式排布的视图.
``` tsx
const Table<TRecord>(props: {
  dataSource: ReadonlyArray<TRecord>
}) => View

这样我们就能

比如一个审批流是什么?

代码抽离复用,将原本放在一块的大量代码缝合在一起,需要为此付出成本.

大组件抽离 => 拆分为多个小的组件(Layout, Content, Controller(交互与业务逻辑))

税费:
父子之间传至 props
全局状态管理 connect
mapStateToProps 和 mapDispatchToProps 实现,
interface 衍生

但是通过 hook 的抽象这部分的税费能获得极大的降低

单体应用 => 微服务架构
每个微服务带来的管理成本:
开发环境维护,CI/CD,版本管理依赖治理
公共组件的管理

CI/CD:Jekins, gitlab
开发环境维护:脚手架(或者开发环境上云?开发环境复用?)(各个子系统依赖版本的管理)
公共组件: Mono Repo 公共基础组件,公共业务组件(通用业务组件,特定业务组件?)(TODO: 还可以再细化一下)

相关链接:
https://blog.christianposta.com/design/the-cost-of-code-reuse-abuse/

微服务的注册中心?

利用nodemon搭建npm的构建脚本

nodemon 是什么?解决什么问题?

官网中的描述如下:

Monitor for any changes in your node.js application and automatically restart the server - perfect for development

也就是说 nodemon 可以帮我们监听文件的变更,然后执行一些命令。
比如:当文件发生变更时自动重启服务,执行构建脚本等。

配置文件的使用:

有 3 种配置方式:

  1. 命令行参数

  2. 项目的根目录下创建 nodemon.json

  3. 直接集成配置到 config.json 中

{
  "name": "nodemon",
  "homepage": "http://nodemon.io",
  "...": "... other standard package.json values",
  "nodemonConfig": {
    "exec": "npm run compile",
    "watch": [
      "src"
    ],
    "ext": "yaml",
    "delay": "300"
  },
}

常用功能选项

参见项目文档

Tips

1. delay 选项在命令行参数中默认单位是秒(s),而在配置文件中默认参数是毫秒(ms)。

2. ignore 选项在使用模式匹配时,模式字符串需要使用引号包裹。

  nodemon --ignore 'lib/*.js'

3. 在 watch 列表中正确的指定扩展名

nodemon 默认只会监听以 .js, .mjs, .coffee, .litcoffee, .json 后缀的文件变更,
所以如果我们需要监听后缀为 .yaml 的文件,就需要正确配置 ext 项,否则 yaml 文件的变更将不会触发对应指令的执行。

  "nodemonConfig": {
    "...": "...",
    "ext": "yaml",
    "...": "..."
  },

如果想要指定多种文件类型,需要使用逗号作为分隔符。

4. 不要再 watch 选项的参数中使用 unix globbing

nodemon 会递归的监视 watch 项指定目录,例如: --watch /src ,所有 src 下的文件都会被纳入监控中。不要使用 unix globbing,例如: --watch ./lib/* ,这样并没有用。

5. 在配置文件中使用环境环境变量

nodemon 支持在配置文件中使用有限的几种环境变量,注意需要使用花括号。

  • {{pwd}} - the current directory
  • {{filename}} - the filename you pass to nodemon
{
  "ext": "*.pde",
  "verbose": true,
  "exec": "processing --sketch={{pwd}} --run"
}

6. 脚本参数与 nodemon 参数发生冲突

可以使用 -- switch 让 nodemon 忽视在其之后的所有参数:

  nodemon app.js -- -L -opt2 -opt3

7. 更多参见

其它更加强大的功能

作为模块或者(子进程)[https://github.com/remy/nodemon/blob/master/doc/events.md#Using_nodemon_as_child_process]使用,当需用 nodemon 创造更复杂的工具时。

参考文档

https://github.com/remy/nodemon

http://www.cnblogs.com/JuFoFu/p/5140302.html

redux-saga 自动状态管理生成

一个简单的利用 ts 类型推导实现的对象工厂函数

const storeFactory = <TState>(state: TState) => {
  const get = <Key extends keyof TState>(key: Key): TState[Key] => state[key];
  const set = <Key extends keyof TState>(key: Key, value: TState[Key]): void => {
    state[key] = value;
  };
  return {
    get,
    set,
  };
};

面向对象的 getter 与 setter

  1. 分离了对字段访问的读,写,减少误操作。
  2. 通过 getter,setter 添加一层抽象隔离,来使得我们可以将数据的存储和数据的使用隔离开。

常见的 redux-saga 下,获取一个数据的模式

一般来说下面的 action,reducer,saga,是放在3个不同的文件目录下

const triggerFetchData = ActionCreator<{ request: string }>();
const setData = ActionCreator<{ data: string }>();

const initState = {
  data: "a message";
};

type State = typeof initState;

const reducer =  (state = initState, action: AnyAction): State => {
  if (setData.match(action)) {
    const { data } = action.payload;
    return { ...state, data };
  };
  return state;
}

export default function* saga() {
  yield takeLatest(triggerFetchData.match, fetchDataWorker);
}

function* fetchDataWorker(
  action: ReturnType<typeof triggerFetchData>,
) {
  try {
    const response: SR<typeof lendApi.fetchLendRelatedOpportunityRecords> = yield call(
      fetchDataApi,
      action.payload,
    );
    const { data } = response;
    yield put(setData({ data }));
  } catch (error) {
    console.error(error);
  }
}

Grid 栅格系统

GridList 相关类库调研

https://github.com/jamiebuilds/react-gridlist

比较有历史了,里面的基本单位用的还是基础的 px,但是基础的建模**是共通的,
通过整体列数的 column 和每一个 Item 的 span 来进行断行, 一行内有其自身的 justify 和 align 规则.

https://github.com/rann91/emotion-flex-grid

https://flexgridlite.elliotdahl.com/

TODO: 功能再看看

react-blocks 用于快速布局,挺好用的

http://whoisandy.github.io/react-blocks/

CheckboxGroup

https://www.lightningdesignsystem.com/components/checkbox-button-group/

可用的参考

ibm https://www.carbondesignsystem.com/
antd Descriptions 组件 https://ant.design/components/descriptions-cn/
google https://material.io/components/
salesforce name-value-list https://www.lightningdesignsystem.com/utilities/name-value-list/

有赞 https://design.youzan.com/design/introduce.html

微软干过的事情 https://en.wikipedia.org/wiki/Fluent_Design_System

杂乱的思考

详情页解决方案:

问题总结,需求与 feature:

  1. 布局:需要能支持灵活的配置详情组件中一组键,值 item 所占的格数
  2. FieldItem:内容布局,键,值放在一行的 item。(已经有部分上下布局的详情了)
  3. FieldItem 内部的 布局和内容?对于 键的表现基本的文本已经能解决了,具体的内容分为:文本,时间戳,自定义组件
  4. 字段项显示控制,典型的相关场景是:根据用户权限来决定某个字段对应的详情项,是否显示,一个 Table 在不同的位置显示不同的地方。

定义一批默认的文本,时间戳,render 用于这些默认行为的渲染,
针对绝大部分的情况可以直接采用全默认配置。

杂乱的存储

ant-design/ant-design#5910

chrome-extension://cdonnmffkdaoajfknoeeecmchibpmkmg/assets/pdf/web/viewer.html?file=https%3A%2F%2Fgithub-production-repository-file-5c1aeb.s3.amazonaws.com%2F34526884%2F958734%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3DAKIAIWNJYAX4CSVEH53A%252F20200327%252Fus-east-1%252Fs3%252Faws4_request%26X-Amz-Date%3D20200327T033257Z%26X-Amz-Expires%3D300%26X-Amz-Signature%3Dee89b66144cdb8409e8b37e7269ec078f4e5a286c318e04a21b3e8d494f5d234%26X-Amz-SignedHeaders%3Dhost%26actor_id%3D18340560%26response-content-disposition%3Dattachment%253Bfilename%253DIoC.AOP.pdf%26response-content-type%3Dapplication%252Fpdf

栅格系统源码分析

什么是栅格系统?
下文我们通过 column 变量代表栅格代表的一行的总列数。
栅格系统将一个区域分成一定的列数,并可以指定一个 Col 组件所能占用的列数。

Row 相关属性

justify 子元素根据不同的值 start,center,end,space-between,space-around,分别定义其在父节点里面的排版方式。
使用 justify-content 来实现

align 子元素竖直方向的对齐方式
使用 align-items 来实现

order 子元素的父元素中的顺序

span 用于定义一个各自所占用的格数
实现方式:

{
    display: block;
    flex: 0 0 ${column / span}%;
    max-width: ${column / span}%;
}

column / span

offset 用于定于向右侧偏

第一步实现一个支持自定义 column 的栅格系统,接口定义如下:

interface ICellContainerProps {
  column: number
}

interface ICellColumnProps {
  span: number;
}

在 npm scripts 中使用命令行参数

通过定义一族指令来实现

这种方式比较逗比

npm-run-script 本身支持的命令行参数传递机制

考虑我们有以下 npm script 脚本:

{
  "scripts": {
    "start": "http-server"
  },
}

当我们执行如下命令时:

  npm run start -- -p 3000

npm 会将 npm run test -- 后面所有的部分,直接拼接在 start 对应的脚本的后面,类似于:

  const start = "http-serve";
  const params = "-p 3000";
  const command = `${start} ${params}`

最后执行了命令:

  http-server -p 3000

但是当我们需要更灵活的使用传递过来的参数的时候,这种方式就无法满足需求了。
考虑如下情况,我们希望能在 my-build 脚本中我们尝试使用 $1 使用传入的参数:

{
  "scripts": {
   "my-build": "npm run format $1 && npm run build",
  },
}
  npm run my-build -- custom-params

最终执行的指令相当于:

  format $1 && npm run build custom-params

也就是说我们想要添加的 custom-params 并不能传递给 format 指令。
在遇到类似场景的时候,命令行参数并不能很灵活的被使用。

利用 sh -c 和 positional parameters 更灵活的使用传入的变量

{
  "scripts": {
   "my-build": "sh -c 'npm run format $0 && npm run build'",
  },
}

最终执行的指令相当于:

  sh -c 'npm run format $0 && npm run build' params

还有利用定义 sh 函数的做法,原理都是类似的,利用 sh 的语法构造出能满足我们需求的 sh 脚本。
这种解决方案依赖于执行 npm 脚本的环境是 linux 环境。如果执行环境换成 windows,那么指令就无法执行。

类似的推广我们也可以进一步利用更多的 shell 的特性,比如环境变量等方式来传递参数。

当前系统中的环境变量传参,就是利用了这种方式。

  "scripts": {
    "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 craco start",
    "build": "sh -c 'cross-env REACT_APP_RUNTIME_ENV=$0 NODE_OPTIONS=--max_old_space_size=4096 craco build'",
    "test": "craco test",
    "ci-test": "cross-env CI=true craco test",
    "archive": "yarn && yarn build && node ./scripts/archive.js"
  },

自定义 js 脚本

比如我们需要去自定义比较复杂的的 build 指令的时候,可以考虑使用自定义脚本来实现。

参考资料

  1. https://docs.npmjs.com/cli/run-script
  2. https://stackoverflow.com/questions/51388921/pass-command-line-args-to-npm-scripts-in-package-json#answer-51401577
  3. https://medium.com/@kevinkreuzer/flexible-npm-scripts-89b5ec0c5b46
  4. https://www.html.cn/archives/8029
  5. https://github.com/testdouble/scripty

前端状态管理 stash

**的碎片

数据按照生命周期去划分:

  1. 应用级,这一类数据的有效性横跨一次用户打开应用到应用关闭:如登录用户的基本信息,一些全局的数据字典等
  2. 单页面级,这一类数据有效性持续到一个单页的开启和关闭:如一个单页页面

数据初始化时机:

  1. 在系统初始化时,一起初始化
  2. 后期由用户的操作触发初始化

如果认为用户进入页面,也是一种操作,应该是系统初始化的时候一起进入

微前端 stash

https://tech.meituan.com/2020/02/27/meituan-waimai-micro-frontends-practice.html

redux 与 rx

https://www.notion.so/RxJS-Redux-CQRS-0a6b2b582a3347dda8accc138ba1396c

挺有意思的

https://juejin.im/post/6844904004007231496

页面级组件的状态怎么耦合?

redux-saga 的模块中页面跳转怎么处理?

方法一:
  直接在 saga 调用 react-router 相关的 push 方法,这种方法是最直观,也是比较自然的方式。
  但是这种方式使得状态管理层,耦合了路由相关的逻辑

方法二:
   在每次方法调用成功或者失败以后,进行状态的转移,这样我们也能够把页面切换的控制权
转交到组件层?页面级组件,利用状态的转移来决定自己的行为

抽时间看看
https://juejin.im/entry/5b50518bf265da0f6436c34a

@@@
https://typeless.js.org/introduction/motivation

作者为某太狼,很接近当前内部使用的技术栈了

https://github.com/sigi-framework/sigi

hox 很强力

https://github.com/umijs/hox/blob/master/README-cn.md
利用一个一个 api 将 useState 提升到全局

umijs useRequest

brickspert/blog#35

ractor

https://github.com/FE-Ractor/ractor/blob/master/readme.cn.md

recoiljs 数据管理

https://recoiljs.org/docs/introduction/getting-started

rematch 笔记

https://rematch.github.io/rematch/#/README?id=getting-started-1

ngrx 笔记

https://ngrx.io/guide/data/architecture-overview

https://ngrx.io/guide/store/metareducers 相当于 middleWare 之于 redux

store => entity

store 只是基于 redux 做了类型相关的接口,
提供了最简单的机制让我们定义 action,reducer,selectors

entity 是一组工具函数,

EntityCollectionService.getAll() 在这个方法调用中,dispatch 了对应的 action 到 store,触发 reducer 的执行。

Entity Metadata 是一个用于描述 Entity 类型的元语言。

ngrx entity

  createEntityAdapter<T>(
    options: { 
      selectId?: IdSelector<T>;
      sortComparer?: false | ComparerNum<T> | ComparerStr<T>;
    } = {}
  ): EntityAdapter<T>

EntityAdapter 是一组由属性和纯函数组成的对象

interface EntityAdapter<T> extends EntityStateAdapter {
  selectId: IdSelector<T>
  sortComparer: false | Comparer<T>
  getInitialState(): EntityState<T>
  getSelectors(): EntitySelectors<T, EntityState<T>>
}


接口文档:
https://ngrx.io/api/data/EntityMetadata

## https://github.com/SebastianM/tinystate
一个更便捷的状态管理工具


## redux-saga-sugar 的问题
https://github.com/jasonHzq/redux-saga-sugar

小小总结:提出了很好的蓝图,或者说大饼?但是易用性总觉得存疑
分层抽象是好的,但是内部层次过多,是否导致后期的定制化过于复杂?

## ngxs 总结
https://www.ngxs.io/


https://www.ngxs.io/concepts/actions

group your actions 相比较于给 action 添加前后缀,然后逐一的从单文件中导出,
通过 namespace  actions 组织到同一个 namespace 下进行统一的管理,
看起来是一个更好的实践。


## 一些其他的地方

## easy-redux
http://callmedadaxin.github.io/2018/05/18/rethink-redux/
https://github.com/callmedadaxin/easy-redux

## 
https://cnodejs.org/topic/5da7d24e865a9844a301bdb4




## dva 模块相关
``` javascript
// 创建应用
const app = dva();

// 注册 Model
app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1 },
  },
  effects: {
    *addAfter1Second(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
});

// 注册视图
app.router(() => <ConnectedApp />);

// 启动应用
app.start('#root');

如果 reducers 和 effects 中有相同的 key 发生冲突怎么办?

mongoDb

https://www.meteor.com/
https://zhuanlan.zhihu.com/p/55080237

angular-redux

https://github.com/angular-redux/store

api

https://zhuanlan.zhihu.com/p/52282779

比较迷的东西

https://juejin.im/post/5c70c700f265da2d8c7dcde3#heading-2

https://zhuanlan.zhihu.com/p/61288928

记录修改代码心得笔记

�修改easy-local-store,store对象的启动逻辑

TODO 原来的代码方式,修改后的代码方式

  TODO

后来在蒋正的提醒下应该注意关注点分离。对象的初始化应该暴露出去,由调用的用户来决定如何使用,在恰当的时机完成初始化。

使用uuid来进行每次通信的标记

使用id来标记,在有多个实例的情况下很可能会串

typescript 类型系统相关 stash

相关类库

https://github.com/millsp/ts-toolbelt

字符串字面量类型

https://dev.to/phenomnominal/i-need-to-learn-about-typescript-template-literal-types-51po

杂乱的记录

https://zhuanlan.zhihu.com/p/57102916

https://fettblog.eu/typescript-type-maps/

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html

  type SagaEffectReturn<T> = T extends (...args: any[]) => any
    ? ReturnType<T> extends IterableIterator<infer K1>
      ? Exclude<K1, Effect>
      : ReturnType<T> extends PromiseLike<infer K2>
      ? K2
      : ReturnType<T> extends Generator<infer K3>
      ? K3
      : ReturnType<T>
    : never;

浏览器HTML渲染过程

当浏览器接收到HTML,到生成我们看到的网页的会经历一下的4个阶段:

1. 解析HTML生成dom树与css规则树

浏览器会parse HTML源文件生成dom tree与css rule tree

2. Render tree的构建

基于dom tree和css rule tree,将会构建出render tree。render tree是由可视化元素按照其显示顺序而组成的树,也是document的可视化表示。它的作用是让我们能够按照正确的顺序绘制内容。
需要注意display: none的DOM子树将不会生成对应的render tree。

3. Layout

当renderer被创建并且被添加到树上的时候,还没有计算出它的位置和尺寸信息。计算这些值得过程被称为layout或者reflow。这个阶段render引擎将会遍历render树,计算出每个节点的尺寸与位置信息。Layout是一个递归的过程,它开始于root renderer。root renderer相当于elements中的HTML document。

4. Paint

这时render tree将会被遍历,由用户界面后端层将每个节点绘制出来。render树的paint计算。

回流与重绘

当dom的可视化属性发生变化的时候,一般而言:

  1. 位置和尺寸的变化时,将导致render引擎重新计算其几何学尺寸,称为回流。
  2. 当非几何学属性变化时,将导致dom的重新绘制。

在实际编码中的例子,什么时候会触发layout, 什么时候会触发paint,

一般说来,当dom发生位置和尺寸大小的改变时,会触发layout的重新计算。
那些具体的API调用会导致?例子和代码。

当dom发生非几何学属性的改变时候,会触发paint的重新计算。

  1. 渲染树布局:
    计算每个element确切的显示位置,也就是layout和reflow的过程。这一过程主要是对每一个render节点的几何尺寸和位置数据的计算
  2. 绘制渲染树:
    通过调用操作系统Native GUI的API绘制将每一个节点绘制出来。
引起回流的常用属性 _ _ _
width height padding margin
display border-width border top
position font-size float text-align
overflow-y font-weight overflow left
font-family line-height vertical-align right
clear white-space bottom min-height

优化

  1. 使用合适的网页分层技术以减少需要重新计算的布局和绘图。
  2. 使用css 3D变形和动画技术。
    当使用3D变形属性,不需要大量的布局计算,不需要重新绘制元素,只需要修改合成时候的属性即可。
    当Compositer需要合成的时候,每个合成层都可以设置自己的3D变形。
    3D变形,DOM子树使用单独的合成层和硬件加速机制,

参考资料

https://csstriggers.com/ 用于查询哪些属性的改变会引起Layout Paint Composite

react-router 相关

静态路由 vs 动态路由
各自的好处与适用范围

提供了一个类型安全的 react-router

https://github.com/AveroLLC/typesafe-react-router
里面声明 route 的方式,也是类似发明了一种 DSL 来描述.

似乎更 trick 一些但是,感觉反而更加的合理?

https://www.type-route.org/

如何:声明 pattern 和 参数类型做到一致的。

import { route, param } from 'typesafe-react-router';

export enum RouteNames {
  HOME = "HOME"
  VIEW_ALL = "VIEW_ALL"
  VIEW_DETAILS = 'VIEW_DETAILS'
}

export const Routes = {
  [RouteNames.HOME]: route('home');
  [RouteNames.VIEW_ALL]: route('view')
  [RouteNames.VIEW_DETAILS]: route('view', param('id'))
}

如果不追求,pattern 和 参数定义的一体化,
我们可以通过泛型的方式传入,然后获取一个 create。


安全的 action 类型
https://www.npmjs.com/package/typesafe-actions#1-basic-actions

看看
http://blog.vavr.io/advanced-typescript-for-java-developers/

用visitor pattern写解释器

TODO Place Holder 施工中

本文尝试以typescript实现一个简单的解释器为例子,通过代码的演化来讲述visitor pattern的使用,以及最重要的:

  1. 为什么要使用visitor pattern。
  2. visitor pattern起到了什么作用。
  3. 如何合理的利用typescript语言的类型推导去帮助我们保证代码的正确。

基础的抽象接口定义

export interface Ast {
  accept(visitor: AstVisitor): void;
}

export interface AstVisitor {
  visitLiteral(literal: Literal): void;
  visitBinaryExpression(binaryExpression: BinaryExpression): void;
  visitUnaryExpression(unaryExpression: UnaryExpression): void;
}

根据经典的visitor pattern,我们先写下接口定义,但是这部分真正的使用会留到后面

抽象语法树的定义

WIKI链接,

export abstract class Expression extends Element {
  abstract get type(): string;

  accept(visitor: Visitor): void {
    visitor.visit(this);
  }
}

// 以下为具体的语法树类型
export class Literal extends Expression {
  constructor(
    readonly value: string | boolean | null | number | RegExp
  ){
    super();
  }

  get type(): string {
    return AstType.Literal;
  }
}

export class BinaryExpression extends Expression {
  constructor(
    readonly operator: string, 
    public left: Expression,
    public right: Expression
  ){
    super();
  }

  get type(): string {
    return AstType.BinaryExpression;
  }
}

这里我们添加一个抽象层次Expression,这里我们多添加一个抽象层来为表达式做一些特定的工作。
用于为AST添加一些专有的方法,这里我们添加了一个type方法来方便获取AST的类型,
当然我们还可以添加更多的方法来帮助我们获取更多的信息和管理AST的各种数据。

独立的访问者

与经典的visitor pattern不同,我们先从一种更直观的实现开始。这种实现可以称之为idependent-visitor,也就是独立访问者。

// 这里我们创建一个log函数来帮助我们输出内容,并且屏蔽平台差异
function log(...args: any[]){
  process.stdout.write(args.map(arg => arg.toString()).join(''));
}

export class IndependentPrintVisitor {
  print(expression: Expression): void {
    switch(expression.type)
    {
      case AstType.BinaryExpression:
        this.printBinaryExpression(expression as BinaryExpression);
        break;
      case AstType.Literal:
        this.printLiteral(expression as Literal);
        break;
      case AstType.UnaryExpression:
        this.printUnaryExpression(expression as UnaryExpression);
        break;
      default:
        throw `IndependentPrintVisitor: unknown Ast type: ${ expression.type }`;
    }
  }

  printLiteral(literal: Literal): void {
    log(literal.value);
  }

  printBinaryExpression(binaryExpression: BinaryExpression): void {
    log('(', binaryExpression.operator, ' ');
    this.print(binaryExpression.left);
    log(' ');
    this.print(binaryExpression.right);
    log(')');
  }

  printUnaryExpression(unaryExpression: UnaryExpression): void {
    log('(', unaryExpression.operator, ' ');
    this.print(unaryExpression.argument);
    log(')');
  }
}

这里我们定义了print方法来提供一个适用于所有具体实现的对外接口。并且在该方法中利用运行时的动态分派,将具体的Ast节点分配到对应类型的处理函数中。这里的节点类型判断也完全可以通过instance of来进行判断,或者在支持反射机制的语言中利用反射来实现。

这种实现的好处是:

  1. 实现了数据(Ast)与对数据的操作(AstVisitor)的解耦:在这种实现中访问者与被访问对象是完全解耦的,Ast节点的所有具体实现,完全不知道还有一个IndependentPrintVisitor的存在。
  2. 所有对Ast节点的操作都聚合的封装在IndependentPrintVisitor中,便于后续对节点操作的修改,在代码管理上是一种很方便的做法。
  3. 与下一节中介绍的经典visitor-pattern实现相比,访问方法名不必局限为visit,因此方法名可以更符合操作的目的,语义化上更好。
  4. TODO 相比较经典的visitor pattern具有更好的自由度。

相对应的坏处就是:
当后续添加新的Ast节点类型时,如果程序员忘了在IndependentPrintVisitor中添加对应的访问方法,会导致错误只能在运行时被暴露出来。

外部访问者

接下来讲解的实现就是《Design Patterns》中visitor pattern的经典实现。
print-visitor.ts:

export class PrintVisitor implements AstVisitor {
  visitLiteral(literal: Literal): void {
    log(literal.value);
  }

  visitBinaryExpression(binaryExpression: BinaryExpression): void {
    const [left, right] = [binaryExpression.left, binaryExpression.right];
    log('(', binaryExpression.operator, ' ');
    left.accept(this);
    log(' ');
    right.accept(this);
    log(')');
  }

  visitUnaryExpression(unaryExpression: UnaryExpression): void {
    log('(', unaryExpression.operator, ' ');
    unaryExpression.argument.accept(this);
    log(')');
  }
}

main.ts:

const expression: Expression = new BinaryExpression(
  '+', 
  new BinaryExpression(
    '-',
    new Literal(1),
    new Literal(2)
  ),
  new UnaryExpression(
    '!',
    new Literal(4)
  ),
);

const printVisitor = new PrintVisitor();
expression.accept(printVisitor);

利用这种方法我们可以通过
TODO
利用语言的类型推导能力,帮助我们保证代码的质量。

现在考虑我们又新添加了一个新的Ast节点UnaryExpreession,�于是编译器给我们抛出了错误

TODO 错误截图

这样利用编译器的类型检查,如果粗心的程序员在添加了新的具体Ast实现后,不小心忘了在printVisitor中添加对应节点的访问方法,编译器会直接给出错误提示。从而避免把问题的暴露推迟到运行时,提高了代码的健壮性。
可能在这个例子中这个好处不是太明显,但是想想当可能有几十颗不同的数据节点的时候,当增删节点类型时,保证visitor对每个现有节点都编写有对应的访问方法将成为一件麻烦的事情。

TODO 参考资料

1.《编程语言实现模式》
2.《Design Patterns: Elements of Reusable Object-Oriented Software》

面试问题准备

盒子模型与box-sizing

box-sizing: content-box 盒子实际宽度 = width + padding + border + margin;
box-sizing: border-box width = padding + border + content-width;

xss攻击

跨站脚本攻击(Cross-site scripting)和SQL注入, 缓冲区溢出攻击类似,从广义上讲都是攻击方通过一些特殊的手段使受害方将提交的数据当做指令执行。

举例: 比如一个类似于百度贴吧的应用,假设前端提交的文本在整个存储和使用的过程中,没有做任何的过滤和转义。那么当后端使用模板引擎生成对应页面时,假设用户在模板引擎中输入<script>xxx</script>,那么这段脚本就会被浏览器解析执行。(字符串拼接和innerHtml是万恶之源)。攻击者可以让这段脚本干各种有情趣的事情比如获取用户cookie等各种重要数据,或者进行csrf攻击。

防御方法: �严格对HTML文本的过滤(正则匹配<,>等特殊符号)和转移(对特殊符号<,>转义为HTML实体)。
设置HTTP-only cookie,该cookie的属性能够阻止客户端脚本访问Cookie。

csrf

说一下原型链,对象,构造函数

DOM事件的绑定的几种方式

一些想法的保存地

2022-06-25

等待审批中
刷算法,刷的头痛,最近效率下降了不少。总结一下,感觉以前方法还是有问题,重复次数不够。

2022-03-06

休息了一个月了,

掌握系统:

  1. 理解和探查系统运行得内在规律,事物是变化得,是有找到变化中的不变形,才能掌握变化
  2. 系统的设计,内部结构需要 scalable
  3. 降低系统的演化成本

2021-01-28

最新好好读读里面 selector 相关的章节

https://react-redux.js.org/api/hooks

读一下

https://immerjs.github.io/immer/docs/introduction

挺有意思的

https://www.apollographql.com/blog/zero-config-graphql-state-management-27b1f1b3c2c3/

2021-01-20

  1. 暗金色 #cebc86;

2021-01-15

  1. 考虑系统的演化,一定要放到时间轴上观察,考虑变化过程中可能重构的成本
  2. 语法要符合人的直觉,通过语法能直接的理解他的语义
  3. https://github.com/topics/grid-layout 很多 repo 令人惊叹

https://marketplace.visualstudio.com/items?itemName=nicoespeon.abracadabra#rename-symbol

2021-01-12

想清楚 update 和 set 的语义

  1. 针对基础类型,update === set
  2. 符合类型,set 是特殊的 update 当 update 更细的是全量的信息的时候等价

sql 语言?

2020-12-24

一篇讲 mda 的文章

dart mixins 的历史

记某个 mixins 函数为 M,父类为 SuperClass
则: NextClass = M(SuperClass)
要让 M 可以复用,M 就需要同特定的 SuperClass 解耦

  1. M 是一个完全独立的单元,内部可以有自己的变量定义,同时内部方法也只满足内部的使用需求。
  2. M 有一些抽象方法,承担了与 M 外部通信的职能,SuperClass 在混入 M 的时候,实现这些抽象方法,完成数据交换。
  3. Dart 通过 on 关键字声明了一个 Trait 需要依赖的或者说限制的类的模式.

2020-12-23

类外的任何代码都不能调用私有方法或访问私有变量,因此它们上没有任何外部依赖关系。
但是这样也就导致部分信息对外部是不可见的,将一个对象看做信源,
那么当我们需要观测它(调用方法取值)的时候,获得的信息本身就具有了不确定性,信源的熵不为 0.

读写分离:使得对系统的观察,与对系统的作用分离开.
现实世界的负责性,往往来自于对系统的观察,也伴随对系统的影响.
举例:人的自省过程。

2020-11-15

fluent interface 是一个语法形式(框架?),用于建立一个流水线(有向图)去 逐步 的收集信息,
函数名,就是当前步骤的名字,而函数参数(数据参数和泛型参数)就是这一个步骤要提供的信息.
而使用者选择函数的过程就是对处理流程的选择与控制.

one-way data flow 与 读写分离,感觉是相同概念的不同表述?

2020-11-12

继承:对象是其自身的模式,我们需要对模式做最大化的复用.以降低系统的复杂度?(混乱度?).

利用现有的类派生新类的方法称为 "差分编程法"(difference programming)

child patttern = father pattern + add pattern
子模式 = 父模式 + 增量模式

而这样的过程称为继承衍生

多重继承:命名空间冲突问题,多个父类内部的方法和状态名互相冲突,
而且多重继承的过度使用,会导致整个实体之间的关系过于复杂.

class 有2个规格,对外部的规格,与对内部的规格,两者的 merge 构成了整个 class 作为 namespace 下的所有元素.

java 的 interface 和 mixin 是被约束过的多重继承

class 继承:实现(逻辑 + 通信协议)的继承
interface 继承:规格(通信协议)的继承

外观:外壳并带有标准的洞,每个洞有专门的名字

java 组合模式,了解一下?

  1. 如果我们将 setter 和 getter 是一种模式,getter pattern,setter pattern,那么 attr_accessor 就是 pattern 的 pattern
    他用于通过一些初始的 meta 衍生出 getter pattern 和 setter pattern

  2. 666

2020-11-03

如何定义问题?
预先定义问题,我们需要找到合理的切分问题的方法.如何去切割一头牛?
有哪些层次与范围?

2020-11-03

数据按照生命周期去划分:

应用级,这一类数据的有效性横跨一次用户打开应用到应用关闭:如登录用户的基本信息,一些全局的数据字典等
单页面级,这一类数据有效性持续到一个单页的开启和关闭:如一个单页页面
数据初始化时机:

在系统初始化时,一起初始化
后期由用户的操作触发初始化
如果认为用户进入页面,也是一种操作,应该是系统初始化的时候一起进入

2020-10-30

当我们再说生产力的时候,是否可以说,当前对物理法则的理解的基础上:

  1. 信息的传播能力,(传播距离,速度)
  2. 能动用的最大的能量
  3. 能动用的最大的算力

而这底层的基本能力,决定了系统能够演进的最大可能,
1888 年电磁学的发现,开启了一扇门,他让地球上任意两点间的通信速度逼近了 C.
整个通信的最大可能速度提升了至少 5~6 个数量级.
从这个时间点开始,系统的更进一步巨大演进就成为了必然会发生的事情.

2020-10-12

微服务设计模式
https://dzone.com/articles/design-patterns-for-microservices

airtable
https://medium.com/@evanhsu__0910/airtable-vs-excel-eeeb969bb7dc

builder 模式:

这个模式与机器无关,是服务于人的.
相比于 javascript,java 没有 object literal 这就导致,一个对象的初始化的方式在语法层面上只有2种方式:

  1. 依赖于构造函数,参数的位置代表来表示初始化的参数
  2. new 出对象后,逐一调用 set

https://cloud.tencent.com/developer/article/1014600

车同轨 书同文 行同伦 郡县制

2020-10-09

信息在系统间传递的过程中,天生具有衰减的趋势
执行力,只是一种对模型简化的尝试,保证了被控制节点回报信息的准确性

世界的底层运行机制,是一种随机性
我在,我看见,世界呈现在眼前

2020-09-26

https://github.com/Vincent-Pang/builder-pattern#readme

Builder 模式
https://github.com/Vincent-Pang/builder-pattern#readme
那么实际上 api 生成,和页面的路径生成也是一个 build 模式的一种应用

2020-09-09

任何一种设计,他的基础 core 假设一定要由普适性.
没有普适性的设计,是糟糕的设计.
core 一定是变化中的不变形.

2020-08-21

这个系统配置和创造的粒度应该更小一点.
应该先从一些合适粒度的业务组件级别开始搭建.
然后再构造出页面.

云凤蝶应该有不少信息可以获取:

  1. 他们现在的通用协议是什么
  2. 架构上理解一下他们走过的路

2020-08-19

  1. 人是一切的目的
  2. 好好工作的人应该被善待

发泄情绪什么问题都解决不了

当特别想发怒的时候,什么都不要说,先让自己的情绪稳定下来
先稳住

很有意思

https://www.w3.org/TR/wai-aria-practices/#disclosure

the-fallacy-of-reuse

https://udidahan.com/2009/06/07/the-fallacy-of-reuse/

微服务,各个系统之间是如何通讯的

SCA(Service Component Architecture)

docs as code

https://www.writethedocs.org/guide/docs-as-code/

云函数

import { hierarchy, tree } from 'd3-hierarchy'

// create a hierarchy from the root
const treeRoot = hierarchy(root)
tree(treeRoot)
// nodes
const nodes = treeRoot.descendants()
// links
const links = treeRoot.links()

Table 的布局算法

https://x-front-team.github.io/2017/04/25/table-layout%E7%90%86%E8%A7%A3%E5%88%B0%E6%94%BE%E5%BC%83/

https://stackoverflow.com/questions/42269301/configure-local-typescript-compiler-inside-package-json
使用项目内,本地安装的 tsc 指令

我们可以把一些业务相关的逻辑:
数据管理+组件,封装到一起

合成 business-modules:

list modules + table 组件
审批记录 + 审批记录组件

api 通过适配器转入

现在项目中面临的问题

现在我们每一个子系统都启动了一个新的项目,而缺乏公共组件,导致同样的代码在多个项目中存在。当遇到有业务逻辑的冲突时,又对相同的代码做了各自的改动。这样随着后续项目增多,业务逻辑变得更加复杂,现有的代码管理会成为一个很大的问题。

我们需要对我们遇到的各种问题进行梳理

感想

  感觉自己对于代码实现,需要思考的东西太少,想的太少,理解的太少。
  真的该好好问问自己,写代码之前真的想清楚了吗?相关的解决方案真的有好好的去调研过吗?
  能把一些想法真正的执行下去,比单纯的想想,要重要得多。

权限系统的总体设计

前端权限系统设计

各种已有的解决方案

react-admin 的前端权限设计

相关文档:https://marmelab.com/react-admin/Authorization.html
使用 来做权限鉴定

import { Authenticated } from 'react-admin';

const CustomRoutes = [
    <Route path="/foo" render={() =>
        <Authenticated>
            <Foo />
        </Authenticated>
    } />
];
const App = () => (
    <Admin customRoutes={customRoutes}>
        ...
    </Admin>
);

react-permission 组件库

https://github.com/IkoroVictor/react-permissions

antd-pro 的权限设计相关文档

相关文档:https://pro.ant.design/docs/authority-management-cn

https://zhuanlan.zhihu.com/p/34665872

http://hcxw.xyz/2018/06/10/ant-design-pro%E4%B8%AD%E7%9A%84%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6/

相关实现:

https://github.com/yesmeck/camas

只是另一个 rbac 仓库
https://github.com/umair-khanzada/role-based-access-control

不是 rbac 更像是一个提供一个写前端策略的方式

https://github.com/stalniy/casl
https://medium.com/dailyjs/what-is-casl-or-how-can-you-build-a-castle-around-your-application-4d2daa0b1ab4?
https://medium.com/dailyjs/managing-user-permissions-in-your-react-app-a93a94ff9b40

https://mjrussell.github.io/redux-auth-wrapper/

https://github.com/brainhubeu/react-permissible

基与 rbac 的实现:
https://github.com/stephenliu1944/easytool-react-permission

一个基础的 rbac 模型的 lib
https://github.com/SkeLLLa/fast-rbac

另外一个 rbac 的解决方案,包含了一些前后端的权限获取和持久化的一些方法
https://medium.appbase.io/securing-a-react-web-app-with-authorization-rules-2e43bf5592ca
https://github.com/appbaseio-apps/todomvc-authorization-client/blob/master/app/auth.js#L92

文档:
https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/#Role-Based-Access-Control--a-Better-Solution

https://blog.vcarl.com/role-based-authorization-react/

如何实现一个基于 rbac 的 react 权限系统

http://v1k45.com/blog/modern-django-part-4-adding-authentication-to-react-spa-using-drf/

wiki:
https://en.wikipedia.org/wiki/Attribute-based_access_control

https://zhuanlan.zhihu.com/p/46284079

stack-overflows:
https://stackoverflow.com/questions/37982473/permission-checks-from-components-in-a-react-redux-application

前端权限解决方案的一般模式

hello world

一般的 checkPermission 函数的接口定

组件

高阶函数

快速搭建 app 相关的东西

https://www.meteor.com/
https://firebase.google.com/

private route

https://medium.com/@thanhbinh.tran93/private-route-public-route-and-restricted-route-with-react-router-d50b27c15f5e


https://alexanderpaterson.com/posts/adding-a-reusable-loading-indicator-to-react-componentshttps://github.com/alex-paterson/react-loading-indicator-component
dva-loading
https://github.com/redux-saga/redux-saga/issues/586副作用拦截器
https://github.com/klis87/redux-saga-requests这个仓库提供了一种提供对 api 拦截的能力基于 axios 的拦截器
https://stackoverflow.com/questions/41635329/axios-response-interceptor-with-combination-with-redux-reducer在 axios 的拦截器中访问 store
https://zhuanlan.zhihu.com/p/63391898

-----------私有路径---------
https://stackoverflow.com/questions/58286901/react-router-redirect-not-working-in-private-route
https://stackoverflow.com/questions/55240578/react-router-dom-protected-route-always-redirects-to-login-during-refresh-page?a=1
https://medium.com/octopus-wealth/authenticated-routing-with-react-react-router-redux-typescript-677ed49d4bd6

react 私有路径的实现
https://dev.to/gloriamaris/using-children-props-for-authenticated-routing-in-react-router-v5-and-react-16-9-3e0m

颜色变量

https://github.com/dt-fe/weekly/blob/v2/118.%E7%B2%BE%E8%AF%BB%E3%80%8A%E4%BD%BF%E7%94%A8%20css%20%E5%8F%98%E9%87%8F%E7%94%9F%E6%88%90%E9%A2%9C%E8%89%B2%E4%B8%BB%E9%A2%98%E3%80%8B.md

https://www.cnblogs.com/xybaby/p/7954610.html

react hooks 杂乱

https://github.com/ecomfe/react-hooks

https://github.com/dt-fe/weekly/blob/v2/120.%E7%B2%BE%E8%AF%BB%E3%80%8AReact%20Hooks%20%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E3%80%8B.md

useState 基于每次用户调用 useState 的顺序不会变化的假设

const [count, setCount] = useState(0);
就好像是给 state 声明了一个变量,并且获得了这个变量的 getter 和 setter

一个机制给用户提供的功能:
用户为了使用这个功能付出的代价 / 用户使用这个功能获得的价值
比如手机的通信功能,他只让用户提供了必要的信息,你想要和谁通话。

后期把一些相关的逻辑抽取到一个自定义 Hook 变得容易,比如说:

function Box() {
  const position = useWindowPosition();
  const [size, setSize] = useState({ width: 100, height: 100 });
  // ...
}

function useWindowPosition() {
  const [position, setPosition] = useState({ left: 0, top: 0 });
  useEffect(() => {
    // ...
  }, []);
  return position;
}

这样提供给了我们一个更便捷的方式去构建:状态和副作用

useReducer 是不是有一点 flux 的味道了

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

特定的 reducerFactory() 生成一堆 action 和对应的 reduce 函数

type ReducerFactory = <TState, TActions>() => {
  reduce: (state: TState, actions: TActions) => TState
};

这样我们就完成了状态转移逻辑的复用,
这样跟进一步我们可以对上面的 dispatch 进行一次封装

const wrappedDispatch = (actions: TAction) => {
  return {
     [actionName]: (payload) => dispatch(actionCreator("特定类型")(payload))
  };
}

这样可以获得一个更类型安全,跟具有复用性的对象,
并且通过 IDE 的类型提示,可以更方便的获取对应的状态。

useMemo 更像是创建一个 selector,用于创建一些 computed 变量,
不过由于不少变量是通过必要传入的,所以需要通过 deps 数组把依赖数组传递进去,
这个实现和 AngularJs 的 deps 的设计也有相似的地方。

为每一个字段生成 getter 和 setter 或是将部分相关字段放到一起

把所有 state 都放在同一个 useState 调用中,或是每一个字段都对应一个 useState 调用,这两方式都能跑通。当你在这两个极端之间找到平衡,然后把相关 state 组合到几个独立的 state 变量时,组件就会更加的可读。如果 state 的逻辑开始变得复杂,我们推荐 用 reducer 来管理它,或使用自定义 Hook。

实现一个依赖注入工具

依赖注入是什么以及为什么我们需要依赖注入?

这里我想通过一个前端开发中的例子来举例:
假设我们系统中依赖于2个基础的服务模块:

StoreService
AppService

而在后续的业务发展中,我们的功能模块渐渐的多了起来,有不少功能模块需要用到这2个服务:

将依赖的项目的实例化托付给一个系统

随着业务的发展,考虑模块和业务变得更加复杂的情况。这个时候通过人工的对依赖进行实例化,整个过程将变得非常的繁琐。这时我们需要考虑创造一个系统帮我们管理这些依赖,并帮助我们进行实例的初始化。
正其名曰:控制反转(Inversion of Control,缩写为IoC)。

Step By Step 100行代码实现一个IOC容器

我们需要一个系统来帮助我们存储,管理依赖及其实例,并能帮助查找和实例化注册到这个容器中的类。
我们先来一个最简略的实现来完成我们的任务。

基础的数据结构与工具方法定义与介绍

  1. provider!!!亚克西
  1. 我们定义Record接口用来作为依赖实例化的记录。
interface Record {
  fn: Function; // fn:即为我们的类的构造函数
  deps: Token[]; // deps:这个类在初始化时的依赖项
  value: any; // value: 初始化的实例对象
}
  1. 其次我们需要一个token到Record的Map来存储依赖token。
export class FlatInjector {
  private _records: Map<Token, Record>;
  ...
  ...
}

开始编码

当准备好以上3者,我们就可以开始对class的依赖查找和实例化进行编写了,过程步骤如下有:

  1. 我们需要一个工具方法computeDeps来在程序运行时获取到类对应的依赖。
  2. 当我们获取到一个依赖列表后,从左向右遍历解析这个依赖列表,通过依赖的Token查询records表,获取到对应的record,如果这个依赖已经实例化那么返回实例,否则实例化这个依赖,对这个依赖进行过程1。
  3. 递归地执行整个过程,�当我们完成所有依赖的实例化后,便可以求值当前record的实例。

现有产品调研

http://inversify.io/

项目构建中的路径配置

在我们的项目中,如果没有好好的使用路径配置,那么项目的路径最后会成为一团乱麻。

1. webpack中的路径配置

2. typescript中的路径配置

瀑布流实现笔记

暂时只是一个杂乱无章的东西罢了

利用getBoundingClientReact接口获取dom的当前位置

let a = document.querySelector('#container');
a.get

这个函数返回的是关于dom几何性质的一组属性,

{
  top: 22;
  right: 802;
  bottom: 328;
  left: 224.34375;
  height: 306;
  width: 577.65625;
  x: 224.34375;
  y: 22
}

这里top,right,bottom,left是dom相对于窗口的位置?,
width,height为dom当前的尺寸
x,y是?

isscroll.js 看看这个的实现

// 1. Skrollr.js中获取scrollTop的方法
// Skrollr.prototype.getScrollTop = function() {
// if(_isMobile) {
// return _mobileOffset;
// } else {
// return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
// }
// };
// 根据文档这个https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
// window.pageYOffset 和 window.scrollY 是一个东西
// var y = supportPageOffset ?
// window.pageYOffset :
// isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;

【笔记】lean cloud 前端开发部署

安装命令行工具

先按照 安装文档 安装 lean cli
NOTE: brew 的安装方式可能因为网络问题不是太好用,建网络比较差时直接使用下载方式安装

初始化项目

按照 文档 进行初始化。

NOTE: 登录错误注意成功登录后,可能执行

  lean init

仍然会提示请登录,需要注意:

  1. 保证lean login登录的账号下有应用,如果没有请登录官网创建一个应用先。
  2. 官网创建应用的节点要和登录的节点一致。

前端动效transition, animation总结

1. CSS Transition

transition定义了dom的属性状态在发生变化的时候显示的动画效果,使得dom的属性状态在2种数值切换的时候能够在显示上更加的平滑。

transition: property duration delay timing-function;

比如我们需要使得一个div的高度变化更加平滑可以使用

img{
    transition: 1s height ease 1s;
}
  1. transition-property: 指定CSS属性的name
  2. transition-duration: transition效果需要指定多少秒或毫秒才能完成
  3. transition-timing-function: 指定transition效果的转速曲线
  4. transition-delay: 定义transition效果开始的延迟时间

2. CSS Animation

参考信息汇总

一篇详细介绍浏览器工作原理的文档:http://taligarsiel.com/Projects/howbrowserswork1.htm

李银城老师的文章:
从Chrome源码看浏览器如何构建DOM树 https://zhuanlan.zhihu.com/p/24911872
从Chrome源码看浏览器如何计算CSS https://zhuanlan.zhihu.com/p/25380611
从Chrome源码看浏览器如何layout布局 https://zhuanlan.zhihu.com/p/25445527
Effective前端6:避免页面卡顿 https://zhuanlan.zhihu.com/p/25166666

前端抓取爬虫总结与调研

5月9日,前端爬虫遇到的问题与解决方案

1 当一个用户接入我们的系统时,如何为用户的页面确定解析规则。

由于用户埋点这个行为是不可知的,我们不知道用户会把点埋在什么地方。用户可能多个不同的页面结构埋同一个点 -> 多个页面结构对应同一个物料集。这时候为用户的页面匹配对应的爬取规则就成为了一个问题。

  1. 每个埋点代码自带一个uuid对应一个解析规则(由我们来配置或交给用户)。再由用户自己去根据每个物料集去配置对应的规则。
    好处:可以完全把解析规则与页面匹配的锅扔给用户。
    坏处:1. 要求用户去学习,这个给用户带来了一定的接入成本。遇到技术能力极低的用户,使用上会比较困难。2. 用户可能会将代码埋到错误的位置,这可能需要我们检测到后反馈给用户。同1遇到技术水平极低的用户这一步会非常困难。

  2. 使用用户网站的url pattern来匹配对应的解析规则,解析规则与匹配的url模式完全由我们配置
    好处:用户完全无感知,方便用户。匹配与埋点位置无关,只和网站的url的路径的模式有关。
    坏处:1. 由于不知道用户会埋点到哪些位置,遇到比较大的站点,完全由我们来完成这一步,工作量会比较大,而且麻烦。如果能和用户有一些必要的沟通,如:了解用户想要我们爬取的页面类型是哪些,可以帮助我们减少工作量,提高效率。2. 遇到用户url路径完全没规律的情况这种方案目前就会失效,但是调研了最近接手的几个用户,暂时没发现这种情况

2 使用完全路径的xpath导致抓取失败

爱范儿网有部分className是通过文章的id动态添加,这导致xpath抓取失败。
例如:http://www.ifanr.com/976865,内容的xpath为:/html/body[@class='post-template-default single single-post postid-1026301 single-format-standard']/div[@Class='o-single']/div[@id='single-content-wrapper']/div[@Class='o-single-content__body o-single-content__body--main']/article[@Class='o-single-content__body__content c-article-content s-single-article js-article']
其中路径上的动态生成的className: postid-1026301导致完全路径的xpath抓取规则失效。

解决方案:
利用css选择器抓取,利用chrome css 选择器生成插件SelectorGadget,生成css selector。
如上例中爱范儿网,使用css选择器.js-article有较好的抓取效果。并且在大部分网站结构中,特定的内容一般都有特殊的对应className对应。利用特殊css类名来进行抓取标记能有很好的效果。
更进一步我们混合使用css选择器(SelectorGadget插件)与xpath选择器(xpath helper插件)来标记内容,能有更好的抓取效果。

几个新用户的调研结果

已接入,已上线的用户就没有做调查了中青在线,第一电动网,电子产品世界。以下是未来将要接入的几个客户站点的调研情况:

客户名 url例子 url模式与网页有确定的映射关系 抓取情况
中彩网 none 抓取成功
爱范儿 http://www.ifanr.com/976865 抓取成功
美篇 https://www.meipian.cn/1a37z058 抓取成功
妈咪宝贝 http://www.mm-bb.cn/?action-viewnews-itemid-24542 抓取成功
卡车之家 http://www.360che.com/news/171022/84920.html 抓取成功
镁客网 http://www.im2maker.com/news/20180509/8b87f2bc0da3c979.html 抓取成功
每经网 http://www.nbd.com.cn/articles/2018-05-09/1215421.html 抓取完成

遇到的新的问题

  1. 互动百科这类的页面,是由人来编辑生成的,难以用xpath这类定死的结构化工具来爬取,通过用css selector能完成部分功能。
  2. 还有部分模块的url是在路径上用参数标识的http://home.mm-bb.cn/space-21578-do-album-picid-169163.html而现在后端对物料是通过url的hostname + pathname来去重,可能策略需要进行一些调整。

结论

  1. 由我们来根据用户的url模式来匹配解析规则,先阶段问题不大。如果能和用户沟通,获取用户想要我们抓取的页面类型,能帮助我们提高工作效率。
  2. 由于一些用户通过url参数来标识不同文章,后端对于物料的去重策略可能需要进行一定调整。

vue开发随意记录

# 全局安装 vue-cli
$ npm install --global vue-cli

# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project

# 安装依赖,走你
$ cd my-project
$ npm run dev

element-ui安装

cnpm i element-ui -S

然后在main.js中写入如下代码

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});

element-ui table column 如果不设置宽度就能均匀分布自适应

生成上线版本,在本地查看

到项目目录下的config文件夹里的index.js文件中,将build对象下的assetsPublicPath中的“/”,改为“./”即可,就在前面加个点就可以了,

git指令速查

webpack 奇怪问题几何

使用ts-loader但是没有在tsconfig.json配置如下,可能导致无限编译的问题
"exclude": [
"node_modules",
"**/*.spec.ts"
]

nextjs项目搭建

遇到的问题

next-sass的使用 [https://github.com/zeit/next-plugins/tree/master/packages/next-sass]

UnhandledPromiseRejectionWarning: Error: Chunk.entrypoints: Use Chunks.addGroup instead

issues地址:vercel/next-plugins#101

是因为安装了webpack4的原因,issue里面有提到虽然webpack4是其中的一部分,但是不能自己安装,
于是删除package.json中devDependencies的webpack项目,然后很暴力的删除node_module重新npm install以后成功启动。

element-ui table的使用

对话框
close-on-click-modal当为false的时候,可以防止用户在点击遮罩的时候关闭模态框
但是注意正确用法是: :close-on-click-modal="false",用为不加:会被解释为传入值为字符串,
所以必须这样才能正确的传入false

work flow stash

相关想法

审批流描述的视角:

  1. 审批流视角: 用于描述整个审批的有向图
  2. 节点视角: 用于描述一个独立节点上的, 详情与表单等信息

相关企业

https://qingflow.com/

aaa的运维笔记本

常用ssh操作与配置

  1. 配置rsa免除密码登录
    首先在local机上
ssh-keygen -t rsa

这个指令会在local机上当前登录用户生成~/.ssh目录下生成id_rsa与id_rsa.pub文件

上传~/.ssh/id_rsa.pub到远程机

ssh-copy-id -i ~/.ssh/id_rsa.pub [email protected]

至此我们再次登录远程主机时,将不再需要密码。

  1. 利用.ssh/config设置服务器别名,简化登录指令
    修改~/.ssh/config设置服务器别名,格式如下:
# 配置格式
Host HOST_ALIAS                       # 用于 SSH 连接的别名,最好与 HostName 保持一致
  HostName SERVER_DOMAIN              # 服务器的域名或 IP 地址
  Port SERVER_PORT                    # 服务器的端口号,默认为 22,可选
  User SERVER_USER                    # 服务器的用户名
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/PRIVATE_KEY     # 本机上存放的私钥路径

# 实际配置例子
host z
  Hostname 127.0.0.1
  User aaa
  PreferredAuthentications publickey
  ServerAliveInterval 60
  IdentityFile ~/.ssh/id_rsa

以后繁琐的:ssh [email protected],可以简化为:ssh x,当然一般为了权限管理我们会有多个账号,
这个时候可以选择不配置User,然后使用ssh aaa@z来登录远程主机。

nginx反向代理配置

常用指令 stash

netstat -tunlp | grep 3000

react-react-app ie 下面不能使用,升级 react-scripts 的版本. 3.5 以上解决

babel 添加可选链语法

  yarn add @babel/plugin-proposal-optional-chaining

然后在 babel.config.js (或者其他的 babel 配置文件)中添加

  plugins: ["@babel/plugin-proposal-optional-chaining"]

动态设置 header

react-helmet 使用

可配置前端render的实现方式

推荐项目组的工作与职责

推荐组,目标是将推荐系统服务化并提供给客户,使得作为一个网站拥有者的客户可以通过接入我们的系统,为网站添加推荐功能。

##推荐系统客户的介入问题

推荐系统最开始通过提供API的方式,用户可以通过系统提供的API,获取推荐内容。然后由客户自行把推荐数据渲染为用户可见的网页内容。同时这种方式要求客户有一定的开发能力:1. 调用API接口,2. 渲染推荐数据为网页内容。

结果在服务客户的过程中发现,广大中,小站点的拥有者几乎都没有任何开发能力。这就导致他们无法接入我们的系统,为了能更好的帮助客户,降低客户的接入成本,我们考虑帮助客户解决推荐物料的获取和视图渲染的问题。帮助客户以0技术成本接入我们的系统。

可配置前端render,解决用户接入的最后一公里

好不容易搭建起来的推荐服务,却因为客户接入过程有开发成本,而将大量的中小型网站挡之门外。
那么既然考虑到大量的客户没有开发能力,就应该由我们去帮助客户, 将获取到的推荐数据,转换为可视网页内容,解决用户接入的最后一公里。

要解决这个问题,我们就需要将前端视图的渲染也一并服务化。

!!!!!!!
TODO 可视化UI搭建!!!!!
!!!!!!!

我们需要给用户提供一个可配置的推荐视图渲染器,使得毫无编程能力的客户也可以通过在系统后台进行简单的操作,从而配置出自己想要呈现的渲染视图。

前端render,系统如何实现?

  1. 首先我们需要在系统后台的前端提供给客户一个图形化的接口,这个系统需要尽量的简单,易用,并且所见即所得,客户配置以后,能马上看到前端渲染出具体样式。
  2. 经过用户的操作,我们获取到一个描述用户渲染需求的配置文件。渲染器获取到配置文件后,从而获取渲染出用户想要的推荐视图。

当时考虑到实现成本,我们选择了操作配置表单的方式来配置自己想要的渲染视图,布局排版类型,以及整个渲染的各种样式。

前端渲染器V1,基于数据绑定的渲染方案

大版本上,我们经过了2个大的迭代,按顺序称为V1,V2版本:
在V1版本中我们使用数据绑定的方式�。前端利用svelte框架,将组件的配置传递给组件,
将配置映射到对应组件的对应样式属性上,然后映射为对应style属性的方式完成配置到组件样式的映射。

采用这种方式我们代码的复杂度公式:

代码复杂度 = (组件结构类型数量) * (可配置基础样式类型数量)

而在为客户服务的过程中,我们发现客户的样式渲染需求多种多样。
为了更好的满足客户的需求,在V2版本中我们添加了更多的排版布局类型,与可控样式选项。
当然潜在的需求并不限制于此,可预见的在后续的迭代中:

  1. 还需要添加新的基础样式支持(如:让所有组件支持圆角配置)
  2. 还需要添加新的布局类型�(如:支持更复杂和多变的图文,类型)

而根据前面的复杂度公式,如果我们采用V1的方案我们将不得不面对以几何级数增长的代码复杂度。
当系统只有4种布局类型,6种样式类型,复杂度为:24,
�而当系统中有12种布局类型,20种样式类型,复杂度为:240。
而显然我们的代码产出或者说人力是有限的,以常量的人力根本无法面对级数增长的需求。我们面临了严峻的问题。穷则思变我们需要思考如何以有限的代码来应对级数增长的需求。

前端渲染器V2,基于编译css代码的生成方案

我们思考当前的需求,虽然样式的配置项非常多,但是基础样式的类型却是有限的。
我们观察组件的可配置项,发现配置的样式类型主要集中在:

  1. 字体相关的样式,如:颜色,字体大小,行高,字体行数
  2. 组件尺寸,如:width, height,margin。
  3. border的配置。
  4. 背景的配置。

由上可见虽然组件的可配置样式多种多样,无非主要集中在上面4种样式配置类型。
那么考虑如果我们不是通过框架的数据绑定,而是通过生成css文件的方式来实现配置文件到组件样式的映射。
同时对于任何一个组件,我们可以看做包含基础样式类型定义的容器。而同时每种类型,我们都可以定义该类型到css样式的转换规则。由组件自身声明他有哪些基础样式类型,我们按照规则把基础样式类型的配置转换成对应的css代码。

(这里只是描述不容易说清楚,下面直接上例子和图:)

TODO 你看到了一张图

而这时,由于json天生的树形结构,我们的配置文件就成为了一种描述组件样式的领域专用语言(DSL),
对于每一种基础样式类型(前面提到的font类,尺寸类,背景类,etc.)定义其到css文件的转换方式,我们就能实现配置文件到css样式文件的转换。

由此我们定义基础样式类型的抽象语法树节点:

// 基础字体样式
export class FontDef extends CssEntityDef {
  readonly name = 'fontDef';

  // 行数和行高会导致几何尺寸设置height的设置
  lineNum: number;
  lineHeight: number;

  family: string;
  size: number;
  color: string; // 字体颜色

  // 加粗斜体下划线
  bold: boolean;
  italic: boolean;
  textDecoration: boolean;
}

// 代表基础的css样式
// 几何尺寸
export class SizeDef extends CssEntityDef {
  readonly name = 'sizeDef';

  width: number;
  height: number;
  paddingTop: number;
  paddingRight: number;
  paddingBottom: number;
  paddingLeft: number;
  marginRight: number;
}

export class BackgroundDef extends CssEntityDef {
  isShow: boolean;
  color: string;
}

export class BorderDef extends CssEntityDef {
  name: string = 'borderDef';
  isShow: boolean;

  // 显示情况:上 下 左 右 上下 左右
  width: number = 1;
  style: string = 'solid';
  color: string;
  radius: number;
}

经过V2版本的重构,极大的增强了渲染器的表现各种样式的能力。
即使后面需要添加所有组件对边框圆角的支持,在渲染器的代码中,我们也只需要添加相应的样式转换规则,只需要添加少量的代码,就能为已有的所有组件扩展新的样式支持。

渲染器上线后的效果与客户的反馈

用户利用前端SDK+可配置渲染器,实现了快速的接入和上线。
我们通过前端渲染器和SDK的开发,使得用户只需要复制粘贴一段js脚本到网站的源代码中就可以实现推荐系统的接入与推荐内容的前端渲染。将一般用户接入的技术成本几乎降为0。

在后续的2个多月中,前端渲染器支持了20+位客户的接入需求。
除了修复了一些少量的BUG外,整个渲染模块结构没有发生变动。

事后有什么反思

TODO - -。。。

react学习笔记

处理事件

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

这里主要注意this的指向问题,需要做一点处理。

React Top-Level API

虚拟DOM创建相关

  1. React.createElement
React.createElement(
  type,
  [props],
  [...children]
)

创建并返回一个新的 React Element 根据所给的type。type类型:string | Component | ReactFragment
2. React.cloneElement
3. React.createFactory(type)
不推荐使用

todo 函数映射

参考:
https://stackoverflow.com/questions/48011353/how-to-unwrap-type-of-a-promise/49889856

export type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

export type MyArgumentType<F> = F extends (...args: infer A) => any ? A : [undefined];

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type MappedOutputs<T> = {
    [P in keyof T]: MyReturnType<T[P]>
}

type FuncMap = { [key in string]: (request: any) => Promise<any> };

type ThenArg<T> = T extends PromiseLike<infer U> ? U : T

// 定义
type MyMappedOutputFuncs<T extends FuncMap> = {
  [P in keyof T]: () => ThenArg<ReturnType<T[P]>>;
}

type MyMappedInputFuncs<T extends FuncMap> = {
  [P in keyof T]: (payload: Parameters<T[P]>[0]) => void;
}

const funcMap = {
  aFunc: async (x1: string) => "123",
  bFunc: async (x2: number) => 123,
  cFunc: async (x1: { a: string, b: number }) => x1
}

type AFuncMap = typeof funcMap;

/**
 * 传入一组函数的 map 返回函数返回值的 map
 */
function getReturnVals(funcMap: AFuncMap) {
  const actions: MyMappedInputFuncs<AFuncMap> = {
    "aFunc": () => {},
    "bFunc": () => {},
    "cFunc": () => {},
  };
  const selectors: MyMappedOutputFuncs<AFuncMap> = {
    aFunc: () => "123",
    bFunc: () => 123,
    cFunc: () => {
      return { a: "123", b: 235 }
    }
  };
  return {
    actions,
    selectors
  }
}


// 混合的 hashMap
type AKey = "a" | "b";
type BKey = "c" | "d";
type A = { [key in AKey]: string } & { [key in BKey]: boolean }  & { [key in string]: number };

爬虫总结

作业要求

不要硬编码,做个比较通用的方案, 来抓取各个网页上的 标题,摘要(或者正文)。
加分项:能够抓取到类目,发布时间,作者,封面图
抓取到之后写个前端页面,把抓取到的信息做一个渲染和展示,展示封面图,标题,摘要,类别啥的
例子域名:
http://news.sina.com.cn/o/2018-04-11/doc-ifyteqtq7611125.shtml
http://news.163.com/18/0411/12/DF436U090001899O.html
http://news.online.sh.cn/news/gb/content/2018-04/10/content_8849088.htm

实现思路

  1. 利用xpath来定位我们需要抓取的资源位置。 那么,对于一类具有相同结构的页面的解析行为,
    我们可以很容易的抽象出一套DSL来描述,例如:
[
  {
    "name": "title",
    "selector": "/html[@id='ne_wrap']/body/div[@class='post_header']/h1"
  },
  {
    "name": "content",
    "selector": "/html[@id='ne_wrap']/body/div[@class='post_header']/div[@id='endText']"
  }
]

name表示字段名,selector用于定位数据在HTML文件上的位置。
那么问题就转化为实现一个由表来驱动的解析器,用于将数据从HTML中剥离出来。
我这里直接抄的神箭手云爬虫的API,神箭手云爬虫应该也是模仿的python下scrapy的实现。
而对应数据的xpath可以通过Chrome的XPath Helper插件来点选获取。

  1. 对于混合在一个字符串中的数据的处理:
  <span>发布时间: 2018-01-02 作者: 香港记者</span>

比如标签中的发布时间信息,是和其他信息混合在一个字符串。采取的方案是获取到整个字符串后,用正则表达式匹配的方式来提取其中的发布时间。

一些市面上已有的产品

搜索云爬虫就能找到不少这方面的产品,而且不少已经做得相当不错。

  1. 神箭手云爬虫
  2. 八爪鱼
  3. 集搜客 gooseeker

他们都选择了实现一套GUI来编写解析规则。使用户可以通过点击网页生成对应的xpath。这样就算是完全不懂程序的人,也能很方便的实现解析规则的编写。
神箭手有开发一款桌面端应用后裔采集器,甚至有win,mac,linux版本。功能也很酷炫,甚至还有图形化编程的功能。
集搜客的GUI是利用firefox的插件系统实现的,利用浏览器的插件机制应该是比较便宜的实现方式。

实现与可选工具记录

  1. 一个爬虫大致可以分为3个大的模块:1. 获取页面 2. 解析页面 3. 数据的持久化存储。获取页面最简单的情况,就是通过语言的基础API,get对应的HTML文件。但是对于真正工业级的爬虫,显然这样是无法满足要求。最可行的方案还是基于已有的成熟框架做二次开发,比如python的scrapy。

  2. 语言选择:javascript Or python。
    javascript的好处是可以很方便的实现客户端和服务器端部分功能代码的复用。如页面的解析功能当需要在客户端提供解析结果预览的时候。但是现在一些工具库的积累上上,不如python强大。
    python的优势在于强大的社区与大量靠谱的备选工具,这意味着大量的问题已经被社区所解决。我们依托这些工具,可以快速的进行开发,并且出了问题也能很快google到。

  3. 可选工具与框架记录
    爬虫框架:python的scrapy,javascript的node-crawler
    headless browser:PhantomJS,puppeteer(Headless Chrome Node API)

  4. 解决前后端分离架构网站爬取与模拟用户行为:可以利用selenium + headless browser的方式。

时间安排

4月11日周3晚:查阅资料,了解有无类似的实现。
4月13日周5:寻找开源工具并使用,各种踩坑不能自拔。
4月14日周6:编写下载和解析的逻辑,用vue糊了一个简陋的数据展示页面。
4月15日周日:整理代码,完善配置文件读取相关逻辑,数据展示页面。完成笔记。

TODO 遇到并解决的一些问题

  1. 利用request获取到的文件,编码转换问题,有时候服务器端返回的文件编码可能不是utf-8这个时候我们必须做一些处理。

  2. xml中使用namespace导致xpath解析失败。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.