Java_006_计算机知识体系结构

计算机体系结构

计算的目的

  • 解决数学问题
    • 表达式 a + b
    • 逻辑判断 if else
  • 现实问题

    • 现实问题-> 数学问题 -> 计算问题
  • 完成计算

  • 计算的基本结构
    • 表达式
    • 逻辑判断
    • 流程控制
    • 读取和存储
  • 基本构造
    • 计算
    • 存储

编程的本质

  • 把现实问题抽象成编程语言中的数据类型(存储)
  • 通过组合编程语言基本元素来表达数据的结构和对数据的转换和操作(计算)
  • 编程就是使用编程语言对现实进行理解和表达

编程语言基本结构

  • 基本数据类型
    • int
    • String
    • boolean
    • float
  • 语句和语法
    • const
    • a = 1+2
    • if
    • for while
    • 函数和方法
  • 执行环境
    • 编译器/jvm
  • 类型模型和类型系统
  • 编程范式
    • 过测式
    • 面向对象
    • 函数式
    • 泛型

程序的执行环境

  • 编译器:将某种语言的源代码转换成目标代码
    • 计算机和人类理解程序代码不同 它只认0和1
  • 解释器:解释执行代码的程序,产生我们要的结果
    • jvm
    • js解释器(浏览器,v8)
  • 编译时和运行时

面试题:静态类型 vs 动态类型 和 强类型 vs 弱类型

  • 静态类型 vs 动态类型
    • 静态:编译时能确定一个变量的类型
    • 动态:运行时才能确定一个变量的类型
  • 强类型 vs 弱类型
    • 强类型:一个对象只有一种解释方式
    • 弱类型:一个对象可以被解释成不同的类型
  • java是静态类型和强类型语言
  • 类型系统是程序语言的一种限制机制,动态类型和弱类型语言更加灵活,但是更容易出错,静态类型和强类型更加容易理解,减少出错的肯能性。

分布式系统

随着互联网的发展,越来越多人有个人设备。一台电脑无法承受数以亿计的访问量

  • 计算 -> 分布式
  • 存储 -> 分布式
  • 把原来在单机上的计算“分布”到多台机器上,单台计算机上的存储“分布”到多台机子上
  • 带来什么问题?
    • 沟通
    • 协作
    • 误差
    • 同步
    • 效率损失
  • 分布的机器数据一致
  • 分布的机器计算协调
  • 分布的机器交流和协作

横向的分布式(计算分到不同机器上)

  • 把计算分到不同机器去执行
  • 把存储数据分布到不同机器
  • 例子:MapReduce
    1
    2
    3
    4
    5
    6
    100T数据 一台机子存不了
    只能分两台 一台50T

    100T数据 里面都存了很多 单词
    如果在一台机子上查很慢
    把文件拆成两半放在两台机子上 同时查

竖向分布式(按功能分)

  • 一组机器负责计算
  • 一组机器负责存储
    • 例子:Lamada ,Casandra 和 Kafka(非常值得学习)

扩展

Java_005_版本控制

版本控制

跟踪和管理代码的变更

  • 反悔的补救
  • 代码的备份
  • 不同版本变更的跟踪和合并管理

开发协作

  • 不同版本、不同开发任务的并行开发
  • 发布版本的跟踪和移动
  • 代码 diff 和 review

git基本原理

基本概念:文件三种状态

  • 变更、未跟踪
  • 跟踪
  • 已提交

变更的基本单位——提交 commit

  • 变更的 hash 值作为 id

分支

  • 不同变更链条的集合

仓库

  • 常说的代码库
  • 所有变更和分支的集合

git基本操作

  • 合并 和 rebase
    1
    2
    3
    4
    5
    合并会生成新的提交
    但是rebase 从不同的提交里再次从新提交一遍到当前分支同时针对每一个提交的冲突进行冲突处理,处理一个冲突

    rebase之后的提交是 流线性的没有merge交叉的情况
    用 rebase最好
1
2
3
4
5
6
7
假设开发流程如下,
master: 0 -> 1 -> 2 -> 6
branch: 3 -> 4 -> 5
其中序号代表开发顺序
那么执行rebase之后就变成了
0 -> 1 -> 2 -> 6 -> 3 -> 4 -> 5
而merge的话则是会对2、5、6三个状态节点进行一个合并,然后生成7这么一个新的节点
  • 撤销更改

Git 安装

Windows安装git

  1. 从官方网站下载Git: https://git-scm.com/download/win
  2. 按照默认选项点击下一步进行安装
  3. 在开始菜单选择 Git Bash, 双击打开命令行窗口,即可执行git命令

推荐可选客户端

  • Cmder: Windows下的命令行模拟器
  • sourcetree: Windows和Mac下的git图形客户端
  • IntelliJ 自带git支持

Mac安装Git

方法一: Homebrew

安装Homebrew, 具体参见Homebrew官方安装文档
运行命令: brew install git 安装git
运行命令: git version 确认安装成功

方法二: Xcode Command Line Tools

1
2
运行命令: xcode-select --install, 然后在弹出窗口中选择安装. 根据网络速度等待大约30分钟, git和xcode相关的开发工具会被成功安装
运行命令: git version 确认安装成功

Ubuntu安装Git

1
2
运行命令: sudo apt-get install git 安装git
运行命令: git version 确认安装成功

其他可选客户端

  • Cmder: Windows下的命令行模拟器
  • sourcetree: Windows和Mac下的git图形客户端
  • IntelliJ自带的git支持

Git 配置

配置用户名

运行以下命令配置用户名和邮箱, 把名字Alex和alex@example.com换成你的名字和邮箱

1
2
3
$ git config --global user.name "Alex Shi"
$ git config --global user.email alex@example.com
运行命令 git config --list 查看修改之后的配置信息

配置文件

配置用户名之后git会在用户目录下面创建一个配置文件 .gitconfig. 例如我们运行上面的命令更改了用户名和邮箱, 则配置文件类似如下:

1
2
3
[user]
name = Yang Shi
email = kid7st@gmail.com

Windows

一般在目录 $HOME 目录下, 注意, HOME是系统变量, 保存的是用户的目录. 运行以下命令则可以找到用户目录:

echo $HOME

####Mac和Linux

配置在用户目录: ~/.gitconfig

推荐配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[user]
name = {Name}
email = {Email}
[alias]
st = status
cm = commit
br = branch
co = checkout
df = diff
rt = remote
lg = log --color --graph --pretty=format:'%C(yellow)%h%Creset -%Cred%d%Creset %s %C(bold blue)(%cn %cr)%Creset'
ll = log --color --graph --pretty=format:'%C(yellow)%h%Creset -%Cred%d%Creset %s %C(bold blue)(%cn %cr)%Creset' --no-merges --stat
up = push origin master
pr = pull --rebase
[core]
excludesfile = /Users/Kid7st/.gitignore_global

Git

Git 官方文档

Git基础

Git分支(branch)

Git工作流

完成如下经典问题

  1. Git常用命令及其功能: git init, git add, git status, git commit, git branch, git rebase, git merge, git push, git pull
  1. 简述”基于Git分支的工作流程”, 其中步骤和使用到的命令是什么?
  1. 请使用Git分支的工作流程
  1. git merge 和 git rebase的差别
1
2
3
4
5
6
7
假设开发流程如下,
master: 0 -> 1 -> 2 -> 6
branch: 3 -> 4 -> 5
其中序号代表开发顺序
那么执行rebase之后就变成了
0 -> 1 -> 2 -> 6 -> 3 -> 4 -> 5
而merge的话则是会对2、5、6三个状态节点进行一个合并,然后生成7这么一个新的节点
  1. 用Hexo在github上搭建自己的博客系统

Java_004_前置知识

linux基本概念

linux架构

本质只有 计算资源 和 存储资源 通过内核进行管理

  • 唯一的接口——系统调用
    敲键盘从物理信号 到 电子信号
  • 内核(抽象化硬件资源,来管理硬件)
  • 硬件

文件系统

  • 接口
  • 抽象——对不同分区分层,解耦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/ 根目录

/home 用户目录

/var 经常变化的文件,如日志

/usr 用户程序/库

/bin 可执行文件

/boot 引导系统启动

/tmp 临时文件

/lib /lib64 系统库文件

/etc 配置文件信息

终端 shell 管道

  • 终端(终端模拟器)
  • shell-linux系统内核和用户交互的接口
  • 管道-程序和程序通信的方式,对接输入和输出
  • linux一切皆文件
    • 标准输入
    • 标准输出
    • 标准错误

命令行

  • ls 查看目录文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ls
    ls -l 查看文件
    ls -al 查看所有文件

    < ls -al
    > -rw-r--r-- 1 admin staff 513 1 10 10:33 a001.ts
    > -rw-r--r-- 1 admin staff 169 1 10 10:34 a002.js
    > -rw-r--r-- 1 admin staff 263 1 10 10:34 a002.ts

    第一字段的后面9个字母表示文件的权限.
    r表是读 (Read)
    w表示写 (Write)
    x表示执行 (eXecute)

    其中前三个表示文件属主的权限,中间三个表示组用户权限,最后三个表示其他用户权限.
  • cd 切换目录

  • mkdir 新建目录
  • pwd 当前目录
  • mv,cp,rm 移动,复制,删除
  • cat 查看文件内容
  • chmod 修改文件权限
  • chown 修改文件所属
  • apt-get 包管理软件
  • vim 文本编辑器
  • sudo 临时获得超级管理员权限执行一些命令
  • man 命令 查看这个命令的帮助文档

    1
    2
    man ls
    man mv
  • | 管道

    • 原理就是把一个命令的输出 当作另一个命令的输入
    • 可以把很多命令结合起来
    • pipeline
  • grep 搜索一些字符串
    1
    2
    3
    4
    5
    # 在 a.txt里查找 a出现的地方
    cat a.txt | grep a

    # 结合管道命令 统计日志里 error 出现的次数
    cat a.log | grep error | wc -l

linux 下目录的构成

  • 绝对路径 以 “/“ 开头的 从根路径开始
  • 相对路径 以”.” 开头 “.”代表当前路径 “..” 代表上一级路径

如何把内容整到文件中呢?

任务

1.Linux操作系统内核的作用和解决的问题

操作系统的主要功能就是操作设备平台的各个硬件,将用户和具体的硬件操作隔离开来,只留下操作接口供使用,这样使用者就不必直接操作硬件,解耦,只需要通过api接口来间接操作硬件。

  1. 内存管理
  2. 进程调度
  3. 硬件设备管理
  4. 用文件系统来管理块存储设备

linux解决的问题

  • 解耦,功能拆分
  • Linux主机允许同时多人上线工作,相比于Windows的单人多任务系统要稳定许多。
  • 它是开源的操作系统,软件成本低

2.Linux 管道的定义和它解决的问题

是一种最基本的IPC机制,作用于进程之间,完成数据传递。

  1. 其本质是一个伪文件(实为内核缓冲区)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端。
  3. 规定数据从管道的写端流入管道,从读端流出。
  • 一个命令的输出做为另一个命令的输入。

解决的问题:

  • 链接多个命令

管道到底是什么

3.了解命令 find, 请搜索相应文档, 并链接到答案里.

find顾名思义就是查找,Linux下find命令提供相当多的查找条件,因此功能比较强大,可以在众多文件或目录下查找你想要的任何文件或目录。

1
2
3
4
5
6
7
8
find ./ -name test.sh 查找当前目录下所有名为 test.sh的文件
find ./ -name .sh 查找当前目录下所有后缀为 .sh的文件
find ./ -name "[A-Z]" 查找当前目录下所有以[A-Z]大写字母开头的文件
find /tmp -size 2M 查找/tmp目录下所有等于2M的文件
find /tmp -size +2M 查找/tmp目录下所有大于2M的文件
find /tmp -size -2M 查找/tmp目录下所有小于2M的文件
find ./ -size +4k -size -5M 查找当前目录下所有大于4k小于5m的文件
find ./ -perm 0777 查找当前目录下权限为777文件或目录

4.了解命令 grep, 请搜索相应文档, 并链接到答案里. 请用命令cat, grep和wc, 管道来统计一个文件中出现关键字 “error” 的行数

5.列出一个自己课程中感兴趣的概念或者知识点, 搜索相关的资料, 并分享到答案.

扩展阅读

UNIX传奇

引导

  • 了解Unix的历史和开源软件的历史
  • 了解UNIX设计原则

书籍

必读

推荐阅读章节

  1. 计算机概论
  2. Linux是什么
  3. Linux如何学习
  4. Linux文件权限与目录配置
  5. Linux文件与目录管理
  6. vim程序编辑器
  7. 认识与学习Bash

扩展阅读 (长期)

任务

  • Linux 入门
  • 命令行 入门
  • Java 开发环境
  • IDE: Intellij IDEA 安装配置
  • 对于 Windows 的同学,可下载安装Cmder,模拟 Linux 命令行环境。

PPT: 前置知识.pdf

Java_003_学习注意事项

什么时候提问题?

  • 一定要会提问,如何问问题,解决问题的能力

技术

  • 核心 java开发基础
    • 算法
    • 设计模式
    • 面向对象
    • 数据结构
  • web开发基础
    • spring/spring boot
    • http / restful api
    • sql / orm
    • 缓存
    • mvc架构
    • cookie和认证
      如何分解知识点/应用/综合
  • 微信小程序
    • 和其他服务的互通
  • java高级特性
    • 异步
    • java8
    • jvm和对象模型

反省机制

  • 60/80/90
  • 怎么是好的,而不是一带而过

半年的达到一个目标

  • 每周至少10小时充电 4小时听 2小时文章 4小时完成练习
  • 持续半年

“停止主观思考” 直接开始行动

  • 方法(语言)并不重要
  • 重复(量变会引起质变的)
  • 寻找高效方法可能会花费很多时间
  • 保持节奏,不要别人在进步你在原地踏步
  • 多问问题。互动
  • 每周日下午2点学习

策略+坚持

  • 任务分解成简单任务
  • 无脑执行任务
  • 回顾反思简单任务提炼知识和技能
  • 应用知识和技能,验证和确认
  • 循环反复

学英语

  • 每天半小时抄单词一个10遍

学算法

  • 找一个算法书,抄
  • 许三多:我是死记硬背

记录+反馈(锚点:而不是一坨文字)

  • 笔记
    • 人会自动忘记痛苦的。这点很可怕。不知道自己失去了什么
    1. 知识点,名词概念
    2. 途中遇到的问题,记录下来(知识的边界,然后回过头来解决)
  • 进程和时间笔记
  • 反馈和改进

解决问题思考模型

  • 需求
  • 关键问题和指标
  • 问题拆解分析
  • 解决方案1,2,3
  • 优缺点限制
  • 最后选型
  • 限制和进化

Java_001_java成长之路

java技术栈

知识体系是固定的

  • 我不是前端
  • 我不是后端
  • 我是程序员

计算机行业

  • 很菜 2000/月
  • 入门后8000/月
  • 大牛后百万/月

不要给自己设限

  • 前端容易达到瓶颈
  • 后端的路更长

为什么学java

  • 生态有了 各种轮子/爬虫/web/大数据
  • 市场被占领了
  • 我被python接口恶心到了
    1
    2
    3
    4
    5
    6
    7
    {
    status:"0"
    }
    // 不同 python 后台很有可能是这个结果
    {
    status:0
    }

前端是切入点

不要把自己局限在前端。

核心竞争力

  • 不是写代码(这是基础)
  • 设计文档
    1. 会出现什么问题(不要加上任何主观判断,和任何假设,用直觉)
    2. 如何解决用什么技术
    3. 每个方案优缺点,不会这个技术怎么办(学)
    4. 回馈变化——不断检验——改善(不停学习,重造)
  • 入手新公司项目(主动)
    1. 不管会不会都接下来,在做的过程中解决。
    2. 中国病态教育,什么东西必须都学会了才敢去做。外国人有魄力直接敢接,做新人没什么责任,尽力去搞,没人让你承担责任,大不了加班,再不济辞职。
    3. 不要把任何事觉得很难,畏首畏尾,你做不了淘宝,可以做一个首页啊!
    4. 项目驱动
  • 不要成为一个搬砖的工人,砖砌的再好,你不知道这个楼以后是什么样的,但是建筑师知道。有后劲,码农——工程师
  • 学习的过程要有反馈。
    1. 你有什么问题, 没有?那我考考你
    2. 固强补弱

经验分享

职业生涯,学什么好

  • 生命有限,不可能所有语言都学会的
  • 起点学什么语言不重要
    1. 每天看书做题,跟做数学题一样
    2. 领扣刷题
  • 技术是不断革新的
    1. 10年前各种框架,10年后还是各种框架,容易迷茫
    2. 需求驱动开发,用自己掌握的技术去实现一个项目。
    3. 回去看你以前写的代码,重构。
  • 跟着需求走一定要有个度
    1. 不要总是重复,没有提升
    2. 战线不要拉的太长,学一个东西,不要想太多,定一个期限不要超过半年,用最笨的方法踏实的去做,对自己宽容一点。然后挑战一下,此时就算失败了就再去做。前半年是让你从新手变成老手。
    3. 如果你在国外一定要去刷领扣 300题两次

提升

  • 不要上来就写代码,先构思
  • 想想会遇到什么问题,如何解决。
  • 深入需求/业务
  • 2个月学vue够吗?先学就好了,不够了2个月之后再去学react,2个月会对你人生造成很大影响吗?不会
  • 明确目标,不要总干体力活。
  • 刻意练习,实践的时候就无脑去做,心无旁骛
  • 有目标,有反馈
  • 给自己一个节点,半年时间去做。
  • 跟着有经验的人去做,多听大牛的建议,不要多想。照做。
  • 不要自我设限,比如学历,没意义
  • 驱动写代码的动力——经济/解决问题有趣

Vue_002状态管理

状态管理是啥

状态是什么

  • UI状态 (不存在数据库里的)
  • 用户状态 (存到数据库里的)

状态管理是什么

  • 父子组件通信

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    父组件 有个 n
    子组件 有个按钮 点击之后触发回调函数 ==> n+1

    这样就导致——耦合(有上有下) 如果自组件里的按钮换成里 element的 btn
    那么还必须要求 element有一个回调函数

    # 解耦
    vue优化的地方提供了一个统一的方法
    this.$on('xxx')
    this.$emit('xxx')
    # 但是这仅仅适合父子通信 如果是爷孙就要 爷父 父子 层层传递,非常麻烦

    # vue 是如何实现让每层的组件 都有 emit/on 方法呢(如何构建整个事件体系的)
    原理是 让所有的 VueComponent extends EventEmit
    原型链 Object--->Vue---->EventEmiter----> VueComponent

    # 不讨论 vuex 之前 跨层级通信 我们可以这样
    var eventBus = new Vue()
    eventBus.$on(xxx)
    eventBus.$emit(xxx)
  • 数据的传递过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    a1的 n 传递给 a4 单向数据流
    要经过 a1 ==> a2 ==> a3 ==> a4
    <root>
    <a1>
    <a2>
    <a3>
    <a4>{{n}}</a4>
    </a3>
    </a2>
    </a1>
    </root>

    但如果用 父子通信就要
    同时 层层 props 传递 n
    a4.$emit(x) ==> a3.$emit(x) ==> a2.$emit(x) ==> a1.$emit(x) ==> root.$emit(x) 非常麻烦

    优化的一个方法是 window.eventHub = new Vue()
    root 那里 eventHub.$on(x)
    a4 那里 eventHub.$emit(x)

ReactWheels03-02环境搭建2

环境搭建2

git小技巧 如何将代码 分别push 到不同仓库

  • 打开你的 github 新建一个空仓库 react-gulu-test-2

    1
    2
    3
    4
    # 初始化后得到从本地上传仓库的命令
    git remote add origin git@github.com:slTrust/react-gulu-test-2.git

    git push -u origin master
  • 我上次的代码仓库是 react-gulu-test-1

    1
    2
    3
    4
    5
    跑到你上次的代码仓库目录 打开命令行
    # git remote add [随便一个名字] git@github.com:slTrust/react-gulu-test-2.git
    git remote add fui-2 git@github.com:slTrust/react-gulu-test-2.git

    git push -u fui-2 master
  • 此时 https://github.com/slTrust/react-gulu-test-2 就有你另一个仓库的所有代码了

修改 package.json 文件

1
2
3
4
5
6
7
8
9
"scripts": {
"start":"webpack-dev-server",
"build":"webpack"
},

这样我们以后就可以

yarn start
yarn build

添加 button.tsx

lib/button.tsx

1
2
3
4
5
6
7
8
9
function Button(){
return (
<div>
按钮
</div>
)
}

export default Button

index.tsx

1
2
3
4
5
import React from 'react';
import ReactDom from 'react-dom';

console.log(React)
console.log(ReactDom)

安装依赖

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
yarn add react react-dom 
# 为保持一直最好跟我一个版本
yarn add react@16.8.4 react-dom@16.8.4


# 运行我们的项目
yarn start

报错
ERROR in [at-loader] ./lib/index.tsx:1:19
TS7016: Could not find a declaration file for module 'react'. '/Users/huangjiaxi/Desktop/hjx-test-1/node_modules/react/index.js' implicitly has an 'any' type.
Try `npm install @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';`

ERROR in [at-loader] ./lib/index.tsx:2:22
TS7016: Could not find a declaration file for module 'react-dom'. '/Users/huangjiaxi/Desktop/hjx-test-1/node_modules/react-dom/index.js' implicitly has an 'any' type.
Try `npm install @types/react-dom` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-dom';`

google 搜索 Could not find a declaration file for module 'react'

点进入第一个url https://stackoverflow.com/questions/41292559/could-not-find-a-declaration-file-for-module-module-name-path-to-module-nam

里面有个赞多的 说这个
npm install -D @types/module-name

由于我们用的 yarn 所以
yarn add @types/react --dev
# 最好加版本
yarn add @types/react@16.8.7 --dev

继续运行
yarn start
还是报错
Could not find a declaration file for module 'react-dom'

根据上次的问题
yarn add @types/react-dom --dev
yarn add @types/react-dom@16.8.2 --dev

再次运行 yarn start 成功

此时看看我的package.json

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
{
"name": "react-wheels-test-1",
"version": "1.0.0",
"description": "react wheels",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
},
"repository": {
"type": "git",
"url": "git+https://github.com/slTrust/react-gulu-test-1.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/slTrust/react-gulu-test-1/issues"
},
"homepage": "https://github.com/slTrust/react-gulu-test-1#readme",
"devDependencies": {
"@types/react": "^16.8.7",
"@types/react-dom": "16.8.2",
"awesome-typescript-loader": "5.2.1",
"html-webpack-plugin": "3.2.0",
"typescript": "3.3",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.7.2"
},
"dependencies": {
"react": "16.8.4",
"react-dom": "16.8.4"
}
}

# 很明显 react的版本不太一致 ,如果以后出了问题最好统一react的版本号,这是前端架构师需要知道的依赖管理。

在看一下 yarn.lock

如果面试问你 yarn.lock是什么?

  • yarn.lock就是yarn的lock文件
  • lock就是把所有依赖锁定的一个文件

在TS项目里 除了安装源代码之外还要安装它的类型声明文件

  • 一般默认帮你安装
  • 如果没安装,你需要手动安装
    1
    2
    3
    4
    # 如果你安装它们
    yarn add react react-dom --dev
    # 就要安装 类型声明文件
    yarn add @types/react @types/react-dom --dev

修改 index.tsx

1
2
3
4
5
import React from 'react';
import ReactDom from 'react-dom';
import Button from './button'

ReactDom.render(<Button></Button>,document.body);

运行 yarn start 结果报错
Can’t resolve ‘./button’ in ‘/Users/huangjiaxi/Desktop/hjx-test-1/lib’

  • 因为tsx的后缀不支持
  • 修改webpack.config.js
  • 添加 resolve
1
2
3
4
5
6
7
8
9
entry: {
...
},
resolve:{
extensions:['.ts','.tsx','.js','.jsx']
},
output:{
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 继续运行
yarn start

# 又报错了 说 React 是一个 全局变量
ERROR in [at-loader] ./lib/button.tsx:3:10
TS2686: 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.


# 原因是 你写的 button.tsx
function Button(){
return (
<div>
按钮
</div>
)
}

return 的内容 里的 div 会被翻译成
React.createElement('div',null,'按钮')
而 button.tsx 里 没有 React

修改button.tsx

1
2
3
4
5
6
7
8
9
10
11
// 引入 react
import React from 'react';
function Button(){
return (
<div>
按钮2
</div>
)
}

export default Button

你也可以这样写,效果是一样的

1
2
3
4
5
6
7
8
import React from 'react';
function Button(){
return (
React.createElement('div',null,'button')
)
}

export default Button

再次运行 yarn start 成功

但是给了一个警告(意思是推荐一个文件244K但是你的文件 259K)

1
2
3
4
5
6
7
8
9
10
11
12
13
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
index.js (259 KiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
index (259 KiB)
index.js

WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
  • 但是我们的index.tsx 里就写了那么几行怎么会那么多呢?
  • 还记得我们 webpack.config.js 里的 mode 吗
    1
    2
    3
    4
    5
    6
    7
    8
    # 修改 webpack.config.js 里的 mode
    mode:'development'

    # 再次运行
    yarn start
    警告消失了

    # 为什么? 因为在 development 时 认为你是在开发模式,所以不会警告文件大的问题

理解 mode的区别

  • 影响 webpack 的提示,如果是 production 就提示文件过大,如果是 development 就不提示
  • development 不压缩代码
  • production 压缩所有代码变成一行

如何解决打包文件过大呢?

  • 不打包React
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 修改 mode
    mode:'production',
    # 添加 externals(不打包的内容)
    # 四个格式是分别对应js历史上各种不同的打包工具的
    {
    commonjs:'react', // nodejs标准
    commonjs2:'react', // nodejs标准
    amd:'react', // 浏览器以前标准
    //<script src="react.min.js"> 等价于 window.React = ...
    root:'React' // 应对既不是nodejs和浏览器 他用的是 script标签引入
    }
    代表针对不同的打包模式
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
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'production',
entry: {
index: './lib/index.tsx'
},
resolve:{
extensions:['.ts','.tsx','.js','.jsx']
},
output:{
path:path.resolve(__dirname,'dist/lib'),
library:'FUI',
libraryTarget:'umd'
},
module:{
rules:[
{
test:/\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'index.html'
})
],
externals:{
// 四个格式是分别对应js历史上各种不同的打包工具的
react:{
commonjs:'react',
commonjs2:'react',
amd:'react',
root:'React'
},
'react-dom':{
commonjs:'react-dom',
commonjs2:'react-dom',
amd:'react-dom',
root:'ReactDom'
}
}
}

先把代码提交

如何来动态切换 各种模式呢?

  • webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = {
    entry: {
    index: './lib/index.tsx'
    },
    resolve:{
    extensions:['.ts','.tsx','.js','.jsx']
    },
    output:{
    path:path.resolve(__dirname,'dist/lib'),
    library:'FUI',
    libraryTarget:'umd'
    },
    module:{
    rules:[
    {
    test:/\.tsx?$/,
    loader: 'awesome-typescript-loader'
    }
    ]
    }
    }
  • 新建 webpack.config.dev.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const base = require('./webpack.config')
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = Object.assign({},base,{
    mode:'development',
    plugins:[
    new HtmlWebpackPlugin({
    template:'index.html'
    })
    ]
    })
  • 新建 webpack.config.prod.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const base = require('./webpack.config')
    module.exports = Object.assign({},base,{
    mode:'production',
    externals:{
    react:{
    commonjs:'react',
    commonjs2:'react',
    amd:'react',
    root:'React'
    },
    'react-dom':{
    commonjs:'react-dom',
    commonjs2:'react-dom',
    amd:'react-dom',
    root:'ReactDom'
    }
    }
    })
  • package.js

    1
    2
    3
    4
    "scripts": {
    "start": "webpack-dev-server --config webpack.config.dev.js",
    "build": "webpack --config webpack.config.prod.js"
    },
    • 此时 运行 yarn serve 打开浏览器 index.js 是 1.2MB (注意如果不是 请把chrome 的 diable cache 钩上)
    • 运行 yarn build 发现 idnex.js 2K
  • 高级处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    你可能看到别人的package.json里是这样的
    cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js
    cross-env NODE_ENV=production webpack --config webpack.config.prod.js

    # 它是跨平台用的(兼容windows),能保证你加的环境变量在各个平台都可以被理解

    # 需要安装依赖
    yarn add cross-env --dev

    # 修改package.json

    "scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js"
    },

lib/index.d.ts 是干嘛的

给使用我们库的人,我们生成了那些类型,但是它应该在 dist目录里

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
48
49
50
51
52
53
54
# 添加 
"outDir":"dist"

修改 tsconfig.json
{
"compilerOptions": {
"outDir":"dist",
"declaration": true,
"baseUrl": ".",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
// "suppressImplicitAnyIndexErrors": true,
// https://github.com/Microsoft/TypeScript/issues/28762#issuecomment-443406607
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true
},
"include": [
],
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts",
"*.js"
]
}

# 运行
yarn build
发现 之前的文件都生成在 dist里了
index.d.ts
button.d.ts

button.d.ts是多余的如何去掉呢?

修改package.json

"main": "dist/lib/index.js",
"types":"dist/lib/index",

配置react 测试框架 jest

google 搜索 react jest

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

# 新建 .babelrc

{
"presets":["react-app"]
}

# 在 package.json scripts里新增测试命令
"test":"cross-env NODE_ENV=test jest --config=jest.config.js --runInBand"

# 新建 jest.config.js 文件

// https://jestjs.io/docs/en/configuration.html

module.exports = {
verbose: true,
clearMocks: false,
collectCoverage: false,
reporters: ["default"],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleDirectories: ['node_modules'],
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
},
testMatch: ['<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)'],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
'^.+\\.(ts|tsx)$': 'ts-jest',
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
}

# 运行 yarn test

报错
yarn run v1.10.0
$ cross-env NODE_ENV=test jest --config=jest.config.js --runInBand
● Validation Error:

Module ts-jest in the transform option was not found.
<rootDir> is: /Users/huangjiaxi/Desktop/react-gulu-test-2

Configuration Documentation:
https://jestjs.io/docs/configuration.html

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

# 安装 ts-jest
yarn add --dev ts-jest

# 继续运行 yarn test

又报错了
$ cross-env NODE_ENV=test jest --config=jest.config.js --runInBand
● Validation Error:

Module <rootDir>test/setupTests.js in the setupFilesAfterEnv option was not found.
<rootDir> is: /Users/huangjiaxi/Desktop/react-gulu-test-2

Configuration Documentation:
https://jestjs.io/docs/configuration.html

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

# 新建 test/setupTests.js

# 再次运行 yarn test
不报错了 提示没找到任何 test文件 走了
No tests found, exiting with code 1

# 新建文件 ./lib/__tests__/hello.unit.tsx

test('hello',()=>{ })

# 此时 test 标红 意思是不知道是啥 你要安装依赖
yarn add @types/jest --dev

# test()不标红了


# 修改 hello.unit.tsx

test('hello',()=>{
expect(1).toEqual(2)
})

# 运行 yarn test 还是爆错

# 用 describe

describe('我的第一个测试用例',()=>{
it('1 等于 1',()=>{
expect(1).toEqual(2)
})
})

# 运行 yarn test 测试生效了
FAIL lib/__tests__/hello.unit.tsx
我的第一个测试用例
✕ 1 等于 1 (6ms)

● 我的第一个测试用例 › 1 等于 1

expect(received).toEqual(expected)

Expected: 2
Received: 1

1 | describe('我的第一个测试用例',()=>{
2 | it('1 等于 1',()=>{
> 3 | expect(1).toEqual(2)
| ^
4 | })
5 | })

at Object.<anonymous> (lib/__tests__/hello.unit.tsx:3:19)

Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.444s


# 修改 测试内容 并运行 yarn test

describe('我的第一个测试用例',()=>{
it('1 等于 1',()=>{
expect(1).toEqual(1)
})
})

// 测试通过
PASS lib/__tests__/hello.unit.tsx
我的第一个测试用例
✓ 1 等于 1 (3ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.552s
Ran all test suites.
✨ Done in 3.32s.

测试 我们的button 能渲染

button.unit.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import renderer from 'react-test-renderer'
import React from 'react'
import Button from '../button'

describe('button',()=>{
it('是个 div',()=>{
const json = renderer.create(<Button/>)
.toJSON()

expect(json).toMatchSnapshot()
})
})

// 运行 yarn test
报错 提示
Try `npm install @types/react-test-renderer` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-test-renderer';`

// 安装 类型依赖
yarn add --dev @types/react-test-renderer

# 继续运行 yarn test
又报错
Cannot read property 'create' of undefined

神奇的 import

上面之所以失败是因为 jest 写 import 必须这样

1
import * as xxx from 'xxxx'

button.unit.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import * as renderer from 'react-test-renderer'
import * as React from 'react'
import Button from '../button'

describe('button',()=>{
it('是个 div',()=>{
const json = renderer.create(<Button/>)
.toJSON()

expect(json).toMatchSnapshot()
})
})

button.tsx

1
2
3
4
5
6
7
8
9
import * as React from 'react';
function Button(){
return (
<div>
按钮
</div>
)
}
export default Button

不想要这个 import * as 怎么办

tsconfig.test.js

1
2
3
4
5
6
{
"extends":"./tsconfig.json",
// "compilerOptions":{
// "module":"commonjs"
// }
}

jest.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// https://jestjs.io/docs/en/configuration.html

module.exports = {
verbose: true,
clearMocks: false,
collectCoverage: false,
reporters: ["default"],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleDirectories: ['node_modules'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
},
testMatch: ['<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)'],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
'^.+\\.(ts|tsx)$': 'ts-jest',
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
}

tsconfig.test.json

1
2
3
4
5
6
{
"extends": "./tsconfig.json",
// "compilerOptions": {
// "module": "commonjs"
// }
}

tsconfig.json

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
{
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"baseUrl": ".",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
// "suppressImplicitAnyIndexErrors": true,
// https://github.com/Microsoft/TypeScript/issues/28762#issuecomment-443406607
// "allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noUnusedLocals": true
},
"include": [
],
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts",
"*.js"
]
}

tslint.json

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
48
49
50
{
"extends": ["tslint:recommended", "tslint-react"],
"rules": {
"no-console": [false, "log", "error"],
"jsx-no-multiline-js": false,
"whitespace": false,
"no-empty-interface": false,
"space-before-function-paren": false,
"no-namespace": false,
"label-position": false,
"quotemark": [true, "single", "jsx-double"],
"member-access": false,
"semicolon": [true, "always", "ignore-bound-class-methods"],
"no-unused-expression": [true, "allow-fast-null-checks"],
"member-ordering": false,
"trailing-comma": false,
"arrow-parens": false,
"jsx-self-close": false,
"max-line-length": false,
"interface-name": false,
"no-empty": false,
"comment-format": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"eofline": false,
"jsx-no-lambda": false,
"no-trailing-whitespace": false,
"jsx-alignment": false,
"jsx-wrap-multilines": false,
"no-shadowed-variable": [
false,
{
"class": true,
"enum": true,
"function": false,
"interface": false,
"namespace": true,
"typeAlias": false,
"typeParameter": false
}
]
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}

button.unit.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import * as renderer from 'react-test-renderer'
import React from 'react'
import Button from '../button'

describe('button',()=>{
it('是个 div',()=>{
const json = renderer.create(<Button/>)
.toJSON()

expect(json).toMatchSnapshot()
})
})

button.tsx

1
2
3
4
5
6
7
8
9
10
import React from 'react';
function Button(){
return (
<div>
按钮2
</div>
)
}

export default Button

运行 yarn test 成功!!!

代码链接

ReactWheels03-01环境搭建

如何快速学会项目搭建

  • 不可能

    只能随机应变,遇到问题就去搜相关 issues 文档和讨论(英文)

步骤

  1. 创建目录和远程仓库
  2. npm初始化
  3. 新建 lib/index.tsx
  4. 新建 webpack.config.js
    1. 配置 entry
    2. 配置 output
    3. 配置 module.rules
      • jsx
      • tsx
      • scss
    4. 配置 plugins
  5. 配置 webpack-dev-server 和 webpack.config.dev.js
  6. 创建 index.html
  7. 配置 webpack.config.prod.js
  8. 创建 examples 预览 与 webpack.config.docs.js
  9. 引入测试
  10. 引入 CI
  11. 配置 tsconfig.json 和 tslint.json
  12. 配置 scripts
    1. yarn start
    2. yarn build
  13. 自定义任务
    1. yarn task create component xxx 快捷入口

实现步骤

  1. 创建远程仓库 如 hjx-test-1 之后 复制 仓库地址

    1
    2
    3
    4
    5
    把仓库拉到你本地 
    cd ~/Desktop
    git clone git@github.com:slTrust/hjx-test-1.git
    进入你的仓库目录
    cd hjx-test-1
  2. 初始化 package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    npm init -y 回车

    安装 webpack https://webpack.js.org/ 找到 get started 点进去
    npm install webpack webpack-cli --save-dev
    但是最好不要用 npm 用 yarn
    // --dev 就是开发者依赖
    // yarn add webpack webpack-cli --dev
    // 为保持一直 跟我一个版本
    yarn add webpack@4.29.6 webpack-cli@3.2.3 --dev
  3. 新建 lib/index.tsx

    1
    csonole.log('hi')
  4. 新建 webpack.config.js

    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
    48
    49
    50
    51
    52
    53
    54
    module.exports = {
    entry: {
    index: './lib/index.tsx'
    },
    module:{
    rules:[
    {
    test:/\.tsx?$/,
    loader: 'awesome-typescript-loader'
    }
    ]
    }
    }

    // 正则 /\.tsx?$/ 代表匹配 .tsx 结尾或者 .ts 结尾的

    // 配置输出路径
    本来想这样 ./dist
    但是不知道为什么 都会引入一个path
    const path = require('path')
    ...
    output:{
    path:__dirname + '/dist'
    }
    ...
    // 此时有一个问题 不能这样 因为不同操作系统是目录的分隔符不同的
    - mac /
    - windows \
    // 所以应该这样
    path:path.resolve(__dirname,'dist/lib')

    // 我们输出的 文件应该有个文件名 实际就是 entry 里的 key

    output:{
    path:path.resolve(__dirname,'dist/lib'),
    library:'FUI',
    libraryTarget:'umd'
    },

    // umd 是什么
    以前 前端 很分裂的
    出现过 require.js 来加载模块 它是 amd 的
    后来 nodejs 出了自己的模块化 它是 commonjs 规范

    require.js 只在浏览器使用
    nodejs 只在 node 里用

    于是你用的时候 就很烦 如何区分你是浏览器还是 node呢?
    - umd 出现了 统一模块定义
    - 所谓 umd 就是根据一些特征选择 不同的定义方式 都没有就是window
    if(define)
    // require.js
    else if (module)
    // commonJS
webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path')
module.exports = {
entry: {
index: './lib/index.tsx'
},
output:{
path:path.resolve(__dirname,'dist/lib'),
library:'FUI',
libraryTarget:'umd'
},
module:{
rules:[
{
test:/\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
}
}
运行 npx webpack
1
2
3
4
5
报错 awesome-typescript-loader 不存在
yarn add awesome-typescript-loader@5.2.1 --dev
# 为什么 --dev因为 只有开发者才用到
报错,说你没安装 ts
yarn add typescript@3.3 --dev
新建 tslint.json
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
48
49
50
{
"extends": ["tslint:recommended", "tslint-react"],
"rules": {
"no-console": [false, "log", "error"],
"jsx-no-multiline-js": false,
"whitespace": false,
"no-empty-interface": false,
"space-before-function-paren": false,
"no-namespace": false,
"label-position": false,
"quotemark": [true, "single", "jsx-double"],
"member-access": false,
"semicolon": [true, "always", "ignore-bound-class-methods"],
"no-unused-expression": [true, "allow-fast-null-checks"],
"member-ordering": false,
"trailing-comma": false,
"arrow-parens": false,
"jsx-self-close": false,
"max-line-length": false,
"interface-name": false,
"no-empty": false,
"comment-format": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"eofline": false,
"jsx-no-lambda": false,
"no-trailing-whitespace": false,
"jsx-alignment": false,
"jsx-wrap-multilines": false,
"no-shadowed-variable": [
false,
{
"class": true,
"enum": true,
"function": false,
"interface": false,
"namespace": true,
"typeAlias": false,
"typeParameter": false
}
]
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
新建 tsconfig.json
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
{
"compilerOptions": {
"declaration": true,
"baseUrl": ".",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": ".",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"strictNullChecks": true,
// "suppressImplicitAnyIndexErrors": true,
// https://github.com/Microsoft/TypeScript/issues/28762#issuecomment-443406607
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true
},
"include": [
],
"exclude": [
"node_modules",
"build",
"dist",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts",
"*.js"
]
}
npx webpack
1
2
3
4
5
6
报错 mode 模式没定义
// 先定义为 生产环境 在webpack.config.js里
mode:'production',

继续 运行
npx webpack 成功

npm install 参数的区别

1
2
3
4
5
6
7
8
9
10
11
12
npm install --save  给用户用

npm install --save-dev 只给程序员用


React --save
Jquery --save 用户那边也要运行

Webpack --dev 只有程序员需要打包
ts --dev 用户那边只用js啊 所以不会有ts

如果你不加 默认 是 --save 的

缩写

1
2
--save  简写 -S
--dev 简写 -D

如果你用yarn

1
2
yarn add  <===> npm install --save
yarn add --dev <===> npm install --save-dev

umd是干啥的?

告诉代码封装成什么样的代码定义

  • commonjs
  • amd
  • umd 兼容前两种

如何做到自动打包呢?

webpack-dev-server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装  webpack-dev-server
yarn add webpack-dev-server --dev

# 为保持一直我的版本是这个
yarn add webpack-dev-server@3.2.1 --dev
# 建议用这个版本 上面的会出现版本不匹配
yarn add webpack-dev-server@3.7.2 --dev


安装完成后运行
npx webpack-dev-server

默认给你一个网址 http://localhost:8080/

http://localhost:8080/index.js
改动一下index.js发现代码就改变了(注意要自己刷新浏览器)

webpack-dev-server 是干嘛的?

  • webpack 是 把一个1.tsx 打包为 1.js
  • webpack-dev-server
    1
    2
    3
    4
    5
    6
    会开一个server 默认是 http://localhost:8080/
    然后
    你访问 http://localhost:8080/index.js 时
    去找 index.js发现没有 但是发现有个 index.tsx
    于是把 index.tsx 翻译成 index.js的字符串 放在内存里
    注意此时的 index.js 并不是 dist里的文件

此时我们还是无法继续写代码,因为只有js 没有 html

如何引入html文件呢

  • 如果我们自己创建一个 index.html 里面引入 ./dist/lib/index.js
  • index.js 的文件名你想改咋办,它是跟 webpack.config.js 里的 entry里 写的
  • 改要改两个地方
1
2
3
4
5
6
7
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<script src="./dist/lib/index.js"></script>
</body>
</html>

自动写入index.js

  • html-webpack-plugin 改写 html的插件

1
2
3
4
5
6
7
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<!-- 由插件写入 -->
</body>
</html>

过程

根目录新建 index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
你好
</body>
</html>
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
yarn add html-webpack-plugin@3.2.0 --dev

改写 webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'production',
entry: {
index: './lib/index.tsx'
},
output:{
path:path.resolve(__dirname,'dist/lib'),
library:'FUI',
libraryTarget:'umd'
},
module:{
rules:[
{
test:/\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'index.html'
})
]
}

修改 index.tsx

console.log('hi4');
const div = document.createElement('div')
div.innerText = 'div'
document.body.appendChild(div);

重新运行
npx webpack-dev-server 刷新浏览器 里面就有了 div

html-webpack-plugin的功能就是在index.html里插入一个script标签引入我们打包编译后的index.js

代码地址

Vue_001路由

前端路由实现思路

  • 路由是什么
  • hash模式?history模式?memory模式
  • Vue-Router源码
  • 正则表达式的使用
  • VueRouter的一些API

什么是路由

先看什么是上网路由

  • 分发请求的对象

你在家里如何访问百度/腾讯/淘宝的呢?

  • 你先得有一个路由器
  • 输入一个地址 如 www.baidu.com
  • 假设你家里是电信的宽带,路由访问电信
  • 电信找到 百度的首页给你返回

停止学习框架,为什么

  • angular
  • vue
  • react

都有各自的路由,你学框架要学三遍

  • 我们应该学好基础

前端路由

需求:实现如下图所示的路由功能(hash路由)

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>

<body>
<a href="#1">go to 1</a>
<a href="#2">go to 2</a>
<a href="#3">go to 3</a>
<a href="#4">go to 4</a>

<div id="app"></div>

<div id="div404" style="display: none;">你要找的内容被狗吃了</div>

<script src="src/index.js"></script>
</body>
</html>

index.js

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
const app = document.querySelector("#app");
const div1 = document.createElement("div");
div1.innerHTML = "1";
const div2 = document.createElement("div");
div2.innerHTML = "2";
const div3 = document.createElement("div");
div3.innerHTML = "3";
const div4 = document.createElement("div");
div4.innerHTML = "4";
const routeTable = {
"1": div1,
"2": div2,
"3": div3,
"4": div4
};

function route(container) {
let number = window.location.hash.substr(1);
// 默认路由
number = number || 1;

// 获取界面
let div = routeTable[number.toString()];
// 兜底路由,当你访问的路由不存在时候
if (!div) {
div = document.querySelector("#div404");
}
// 展示界面
container.innerHTML = "";
container.appendChild(div);
}

route(app);

window.addEventListener("hashchange", () => {
console.log("hash 变了");
route(app);
});
  • 重点是 监听 hash 改变的事件 hashchange
  • 在线代码

嵌套路由实现(只说原理不写代码)

继续以 hash 模式来说

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

如何嵌套呢?
只能这样
#1/1.1
#1/1.2
#1/1.3

// 思路
// 假如 当前路由是 #1/1.1
let routerPath = window.location.hash.substr(1); // 1/1.1
let arr = routerPath.split('/') // ['1','1.1']

再去进一步的匹配路由

hash / history / memory 路由的区别

  • hash 任何情况下都可以使用的前端路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    缺点 SEO 不友好(根源是因为服务器收不到 hash)

    打开 chrome 访问 https://www.baidu.com#1
    点击 network
    点击 network
    点击 network
    发现,url 为 https://www.baidu.com hash被吃了

    结论:浏览器不会把 # 号之后的内容发给浏览器的
    这就导致:
    https://www.baidu.com#1
    https://www.baidu.com#abc
    https://www.baidu.com#hjx

    三个页面有自己的内容

    最后实际请求的都是 https://www.baidu.com

    这样 google收录后永远都会展示默认页面https://www.baidu.com 的内容(默认路由)
  • history 后端把所有前端路由都渲染同一页面

    1
    唯一缺点 IE8以下不支持

history 模式

index.js

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
48
49
50
51
52
53
54
const app = document.querySelector("#app");
const div1 = document.createElement("div");
div1.innerHTML = "1";
const div2 = document.createElement("div");
div2.innerHTML = "2";
const div3 = document.createElement("div");
div3.innerHTML = "3";
const div4 = document.createElement("div");
div4.innerHTML = "4";
const routeTable = {
"/1": div1,
"/2": div2,
"/3": div3,
"/4": div4
};

function route(container) {
let number = window.location.pathname;
console.log("number: " + number);

if (number === "/") {
number = "/1";
}

// 获取界面
let div = routeTable[number.toString()];
if (!div) {
div = document.querySelector("#div404");
}
div.style.display = "block";

// 展示界面
container.innerHTML = "";
container.appendChild(div);
}

const allA = document.querySelectorAll("a.link");

for (let a of allA) {
a.addEventListener("click", e => {
e.preventDefault();
const href = a.getAttribute("href");
window.history.pushState(null, `page ${href}`, href);
// 通知
onStateChange(href);
});
}

route(app);

function onStateChange() {
console.log("state 变了");
route(app);
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>

<body>
<a class="link" href="/1">go to 1</a>
<a class="link" href="/2">go to 2</a>
<a class="link" href="/3">go to 3</a>
<a class="link" href="/4">go to 4</a>
<div id="app"></div>

<div id="div404" style="display: none;">你要找的内容被狗吃了</div>

<script src="src/index.js"></script>
</body>
</html>

注意

  • 如果单纯改变 pathname 每次改变路由到会造成页面刷新

    1
    window.location.pathname // 默认会带一个 "/" 开头
  • 必须结合 history API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    如果你用a标签触发路由改变,要阻止a它的默认事件(不阻止就刷新页面)
    // 劫持点击事件
    aLink.addEventListener('click',(e)=>{
    // 阻止默认事件
    e.preventDefault();
    // 触发 history API
    const path = aLink.getAttribute('href');
    window.history.pushState(null,null,path);
    // 通知 路由事件
    onStateChange();
    })

memory 模式(前端一般不用,因为没有任何url) 单机版路由

原理就是把 路由存在 localStorage /或者本地里

1
2
3
4
此模式适合非浏览器 
- app
- react native
- weex

index.js

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
48
49
50
51
52
53
const app = document.querySelector("#app");
const div1 = document.createElement("div");
div1.innerHTML = "1";
const div2 = document.createElement("div");
div2.innerHTML = "2";
const div3 = document.createElement("div");
div3.innerHTML = "3";
const div4 = document.createElement("div");
div4.innerHTML = "4";
const routeTable = {
"/1": div1,
"/2": div2,
"/3": div3,
"/4": div4
};

function route(container) {
let number = window.localStorage.getItem("xxx");

if (!number) {
number = "/1";
}

// 获取界面
let div = routeTable[number.toString()];
if (!div) {
div = document.querySelector("#div404");
}
div.style.display = "block";

// 展示界面
container.innerHTML = "";
container.appendChild(div);
}

const allA = document.querySelectorAll("a.link");

for (let a of allA) {
a.addEventListener("click", e => {
e.preventDefault();
const href = a.getAttribute("href");
window.localStorage.setItem("xxx", href);
// 通知
onStateChange(href);
});
}

route(app);

function onStateChange() {
console.log("state 变了");
route(app);
}

Vue-Router

如何看源码

  • 从你最常用的地方看 router-link
    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    1.打开代码链接 
    2.搜 link 发现有个 Link 对象(vue的组件) 往下看 return 返回信息 里面有个
    return h(this.tag, data, this.$slots.default)
    1. 这个 h 是啥?
    2. vue 除了提供模版渲染的方式,还提供了 一个 render函数
    可以让你用 js 的方式创建任意元素
    3. 理解完 h函数 我们继续看
    return h(this.tag, data, this.$slots.default)
    - this.tag 代表你要渲染的标签
    - data ? 又不知道了
    - this.$slots.default 插槽 标签里的内容 不重要
    4. 看data是啥
    var data = {
    class: classes
    };

    if (this.tag === 'a') {
    data.on = on;
    data.attrs = { href: href };
    } else {
    var a = findAnchor(this.$slots.default);
    if (a) {
    a.isStatic = false;
    var aData = a.data = extend({}, a.data);
    aData.on = on;
    var aAttrs = a.data.attrs = extend({}, a.data.attrs);
    aAttrs.href = href;
    } else {
    data.on = on;
    }
    }
    5. data.on ? 应该是个事件继续检索信息
    var on = { click: guardEvent };
    if (Array.isArray(this.event)) {
    this.event.forEach(function (e) { on[e] = handler; });
    } else {
    on[this.event] = handler;
    }
    6. handler 是啥?
    var handler = function (e) {
    if (guardEvent(e)) {
    if (this$1.replace) {
    router.replace(location);
    } else {
    router.push(location);
    }
    }
    };
    7. 看 router.push(location); 熟悉吗?
    router 是那里定义的?
    继续看
    var router = this.$router;

    $router是啥!这时就要脑补了

    // 参考vue-router文档里
    const routes = [
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
    ]

    // 3. 创建 router 实例,然后传 `routes` 配置
    // 你还可以传别的配置参数, 不过先这么简单着吧。
    const router = new VueRouter({
    routes // (缩写) 相当于 routes: routes
    })

    // 4. 创建和挂载根实例。
    // 记得要通过 router 配置参数注入路由,
    // 从而让整个应用都有路由功能
    const app = new Vue({
    router
    }).$mount('#app')

    这里的router 就是 VueRouter 继续找

    10 找到了 push 定义的地方
    VueRouter.prototype.push = function push (location, onComplete, onAbort) {
    this.history.push(location, onComplete, onAbort);
    };

    11 发现了 里面路由的各种模式定义
    switch (mode) {
    case 'history':
    this.history = new HTML5History(this, options.base);
    break
    case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback);
    break
    case 'abstract':
    this.history = new AbstractHistory(this, options.base);
    break
    default:
    {
    assert(false, ("invalid mode: " + mode));
    }
    }

    1. 就看 HTML5History 模式
    HTML5History.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;
    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(location, function (route) {
    pushState(cleanPath(this$1.base + route.fullPath));
    handleScroll(this$1.router, route, fromRoute, false);
    onComplete && onComplete(route);
    }, onAbort);
    }

    1. 继续看 transitionTo 是啥

    History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
    var this$1 = this;
    var route = this.router.match(location, this.current);
    this.confirmTransition(route, function () {
    this$1.updateRoute(route);
    onComplete && onComplete(route);
    this$1.ensureURL();

    // fire ready cbs once
    if (!this$1.ready) {
    this$1.ready = true;
    this$1.readyCbs.forEach(function (cb) { cb(route); });
    }
    }, function (err) {
    if (onAbort) {
    onAbort(err);
    }
    if (err && !this$1.ready) {
    this$1.ready = true;
    this$1.readyErrorCbs.forEach(function (cb) { cb(err); });
    }
    });
    };

    1. 看出 onComplete 是成功的逻辑处理
    this.transitionTo(location, function (route) {
    pushState(cleanPath(this$1.base + route.fullPath));
    handleScroll(this$1.router, route, fromRoute, false);
    onComplete && onComplete(route);
    }, onAbort);

    15 看 pushState 又熟悉了
    function pushState (url, replace) {
    saveScrollPosition();
    var history = window.history;
    try {
    if (replace) {
    history.replaceState({ key: _key }, '', url);
    } else {
    _key = genKey();
    history.pushState({ key: _key }, '', url);
    }
    } catch (e) {
    window.location[replace ? 'replace' : 'assign'](url);
    }
    }

h函数是啥?

我们想实现一个 传递 level参数 就渲染 h1~h6的 自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<ah :level="1">Hello world!</ah>
当我们开始写一个只能通过 level prop 动态生成 heading 标签的组件时,你可能很快想到这样实现:

<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</script>

是不是很傻

  • 用 render 函数 重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子元素数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})

h 就是 createElement

h 就是 createElement

h 就是 createElement