Code Monkey home page Code Monkey logo

bilibili-up-share's Introduction

这里是 B 站 UP 主 前端诗人

程序员最重要的精神之一:分享(share),这次我的身份是B站UP主,将会把视频提到的代码都放在这个仓库,喜欢的话请持续支持吧

最近更新时间 2023-01-14 2021-06-04

Quickstart

clone 仓库

你可以 clone 这个仓库,查看视频中的演示代码

旧的仓库地址: https://github.com/yayxs/vast-video-tutorial.git

[git clone https://github.com/yayxs/bilibili-up-share](https://github.com/yayxs/bilibili-up-share.git)

View all branches

git branch -a
books-share
master
typescript-share
vitepress-demo
vue3-learn
vuepress-site
remotes/origin/HEAD -> origin/master
remotes/origin/books-share
remotes/origin/front-end-interview
remotes/origin/master
remotes/origin/typescript-share
remotes/origin/vitepress-demo
remotes/origin/vue3-learn
remotes/origin/vuepress-site

切换到你需要的分支 然后就可以愉快的查看案例代码了

git checkout xxx // xxx 代表你想切换的分支

All branchs

Current branch git repo Link b 站系列视频 Link
vuepress-site https://github.com/yayxs/bilibili-video-tutorial/tree/vuepress-site 2020 年 9 月更新 基于 VuePress 轻松实现个人博客站点教程
books-share https://github.com/yayxs/bilibili-video-tutorial/tree/books-share Web 大前端书籍推荐分享(JavaScript 等)
front-end-interview https://github.com/yayxs/bilibili-video-tutorial/tree/front-end-interview 前端头部的高频面试题

Author

Changelog

  • 2023年1月14号vitepress的分享 仓库的名字修改 (vast-video-tutorial 修改为了 bilibili-up-share)
  • 2021 年 06 月 10 号 :包管理修改为pnpm
  • 2021 年 06 月 04 号 :主要修改了仓库的名字(bilibili-video-tutorial 修改为了 vast-video-tutorial)
    • 新增了 vitepess 新建站点的分享(包含部署上线)
  • 2020 年 11 月 19 日:主要迁移了前端面试(TOP-FE-IQA 的代码)包括:JS 数组的去重 + 手写 new 操作符 等等(详细代码请在 front-end-interview 这个分支查看 )

Show your support

如果你感觉代码对你有帮助请不要忘了 标星 ⭐ 也不要忘了在 B 的视频一键三连 分享让更多关注这件事的新朋旧友看到,这样更新才有动力说实话讲道理

bilibili-up-share's People

Contributors

yayxs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

bilibili-up-share's Issues

全栈之路 | 打造专属互联网名片,有力表达自我价值

  • 系列主题 《全栈之路》
  • 讨论框架:NestJS
  • 文章词数 :
  • 第几篇:No.1
  • 分享类型:如何使用
  • 源码地址:
  • 微信公众号:《前端厚说》

前言

到底什么是前端?每天醒来我都在问自己,再也不是三五年前甚至一两年前的API写手 了。怎么才能提升自己社会的价值感,你若盛开,总有不知道什么动物要来

可能对于前端小伙伴来说,还不知什么是nestjs 框架,甚至还分不清什么是nextjs nuxtjs nestjs 在技术调研的时候,我也看到有些文章 拿它们三个来横向对比(nestjs 和其他二者还是有点区别的)。不过这并不重要,其实想分享这个框架一段时间了,不知不觉半年多都过去了了。事实证明nestjs 确实发展的很好 (当时的版本还是nest.js + @nestjs/[email protected]

伴随着窗外的雨声,话不多说,直接开始……

关于NestJS

阅读文档

更新项目依赖

如果我们想更新一下package 中的依赖包的话可以

更新一下依赖 yarn upgrade-interactive [--latest]

yarn upgrade-interactive [--latest]

选型介绍

内置并完全支持TypeScript 大体来看可以使用ts

  • OOP
  • FP
  • FRP

脚手架安装

在设备全局安装脚手架,使用 npm i -g @nestjs/cli,或者使用yarn cnpm 道理相似,在此略过

倘若你感兴趣的话可以读一下之前的分享在笔者《把发呆的时间拿来写笔记》

react牵手nestjs打造帖子后台管理

$ npm i -g @nestjs/cli
$ nest new nest-post-api // nest-post-api 为项目的名字

熟悉脚手架

一般的脚手架都是可以看下,我们可以有什么命令,快捷的命令nest -h

项目初始化

接下来我们要做的就是利用脚手架创建一个船新的项目

  • Which package manager would you ❤️ to use?
    使用npm 或者yarn 都可以

    项目初始化完成之后,就是这个样子

简单接口

import { Controller, Get } from '@nestjs/common';

@Controller('posts')
export class PostsController {
  @Get()
  index() {
  // 返回的数据
    return [{ id: 1 }, { id: 1 }, { id: 1 }, { id: 1 }];
  }
}

项目架构

项目起名字真是个费劲的活儿,暂且就叫做 nestjs-server 主要作用是提供接口服务。其中

  • admin 主要是为管理后台提供接口(也就是所谓的 后台管理系统 )
  • web 主要是为web平台提供服务 (也就是所谓的面向C端用户的web网站)

那么我们怎么在一个nestjs 项目里管理 两个甚至多个项目呢。那么就要提到nest js 的工作模式( workspaces

  • 标准模式:运行时nest new,将使用内置原理创建一个新项目

  • Monorepo模式 要启用monorepo模式,请从标准模式结构开始,然后添加项目

nest generate app web // 新建一个项目主要是web网站

tree 查看工程结构

 tree -C -L 3 -I "node_modules"
├── index.html
├── main.js
├── test
   ├── jest-e2e.json
   └── app.e2e-spec.ts
└── src
    ├── main.ts                 # 
    ├── aapp.service.ts         # 
    ├── app.module.ts           # 
    ├── app.controller.spec.ts
    └── app.controller.ts
    

这里就贴最基本的吧,完整版的可以在Github 😊 ​仓库上看到本篇的所有源码 。我们先不说要做个什么,反正想到什么就去做

论种树的最佳时间,其次就是现在

还记得我们起的端口,那么就先把项目跑起来看看吧

安装其他模块

一上来就要安装其他的模块,难道不是简单的 小踩坑 ,有些东西不知道是不是年级大了,

好记性不如烂笔头,这也是我无聊的时候 就去写写文章的原因

cookie-parser

How does nestjs get the cookie in the request?

也就是说在我们的 nestjs 怎么取到 cookie 值 ,你可以用小手点一下上边的链接

express-session

express-session-gh 主要是处理服务器环境数据存储

hbs

一个模板引擎,主要是渲染页面用的,(不知道能不能用上)这个模板引擎主要是为了呈现咱们的HTML视图,也就是所谓的MVC 应用。官网使用 的是 hbs

yarn add cookie-parser express-session  hbs

截止目前咱们的 package.json

 "dependencies": {
    "@nestjs/common": "^7.0.0",
    "@nestjs/core": "^7.0.0",
    "@nestjs/platform-express": "^7.0.0",
    "cookie-parser": "^1.4.5",
    "express-session": "^1.17.1",
    "hbs": "^4.1.1",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^6.5.4"
  },

最新的稳定版本

静态资源

首先要做的是配置静态资源文件目录,比如图片等。

首先我们使用express 平台,在默认的情况下nest在后台会使用express 库

import { NestExpressApplication } from '@nestjs/platform-express';
  const app = await NestFactory.create<NestExpressApplication>(
    AppModule,
  );

我们告诉Express,该public目录将用于存储静态资源

模板引擎

 app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');

我们通过简单的配置可以使用模板语法来来渲染html

中间件

cookie 中间件

app.use(cookieParser('myCookie'));

session 中间件

 app.use(
    session({
      secret: 'mySession',
      resave: true,
      saveUninitialized: true,
      cookie: { maxAge: 1000 * 60 * 30, httpOnly: true },
      rolling: true,
    }),

main.ts 文件中使用的中间件是全局中间件,会对应用的整体做些拦截等等一些,中间的事情。

阶段测试

那我们在如上的一段操作之后,我们可以简单的测试一下,比如我们把

image-20200530230450413

一张图片放在服务的public 下,ok 是可以访问到的

工程架构

这才是正文的开始,一个良好的编程之旅绝对是一个干净整洁的目录开始的,一些开源的框架或者是库,在提供给开发者使用的时候并没绝对要求怎么建文件。不过,,随着开发规范的形成,我们大体也能见名知意

module

我们都知道,nestjs 的一个一个模块的,拥有很好的开发体验,所以我们新建3个子模块(这是指的相对于APP主模块而言)

nest g module module/admin
nest g module module/web

其中,

  • admin 是管理后台
  • web 是面向C端用户
|-- src
|   |-- app.module.ts
|   |-- common
|   |-- extend
|   |-- interface
|   |-- main.ts
|   |-- middleware
|   |-- module
|   |   |-- admin
|   |   |-- web
|   |-- schema
|   |-- utils
|      | -- config.ts

登录接口

登录JWT Passport策略

一般情况下,后台管理系统登录的返回结果通常有一个token 我们接下来先来开发个登录接口

官网-关于身份验证的方式

身份验证是大多数应用程序的重要组成部分,在生态中有不同的策略和方法,最为流行的是 passport 身份验证库

大致的流程

image-20200531125253672

接口文档

既然要开始写接口了,我们的nestjs 是十分相当的便捷。一套简明扼要的接口 其中提供出的文档是必要,可以通过OpenAPI(Swagger) 只需要main.ts 中配置就可以了。·

const options = new DocumentBuilder()
    .setTitle('@yayxs')
    .setDescription('基于nest.js后端接口服务')
    .setVersion('1.0')
    // .addTag('cats')
    .build();

  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('nest-api-docs', app, document);

image-20200531133902547

连接数据库

nestjs 本身与数据库是没有什么强联系的,不过我们需要。我们连接数据库的目的就是持久化数据。我们选择数据库就选择

  • MongoDB
  • typegoose

【读书】 《现代 JavaScript 》

[Reading-Thinking] 现代 JavaScript

2020-07-01

  • 关于引擎 不同的浏览器

  • 手写 ajax

  • 本地存储的方式有哪些 有什么异同点

    特点区别

  • 什么是同源策略 怎么解决跨域问题呢

  • 刚起步重点是规范,这是起点

  • [https://www.google.com/search?q=mdn%20parseint&ie=utf-8](https://www.google.com/search?q=mdn parseint&ie=utf-8)

  • 控制台多行输入代码

  • js 代码中的错误 有哪些类型 error

    • SyntaxError
  • https://plnkr.co/

  • jsonp 的原理


  • 深浅拷贝 手写 深浅拷贝

  • letconst var

  • JavaScript 也是有类型的不是吗?

    动态类型

  • 数据类型

    • bigint
  • null 和 undefined 区别的点在哪

  • typeof null` 会返回 `"object"` —— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个 `object
    
  • 自增自减

  • 字符串的比较算法

    • sort()函数是怎样进行排序的
    • Unicode 编码顺序

2020-07-02

  • 逻辑或 是怎么运算的

    一个或 "||" 运算的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。

  • ?? 运算符

  • 局部变量

  • 全局变量

  • Js 的默认参数可以是一个函数的调用

  • 创建函数的方式

    • 函数声明
    • 函数表达式
  • 回调函数

  • 什么是函数的声明提前

首先 函数有两种形式 目前来看的话 1是 函数声明 2 表达式 不同的是

  • 初始化在全局寻找 函数的声明 ,完事之后执行代码 那这样
  • 不过函数声明会有一个块级作用域
  • 创建 对象的 方式有?

    • 构造函数
    • 字面量
  • 属性名(key)必须是字符串

    let obj = {

    0:'hah'

    }

    console.log(obj[0])

    console.log(obj['0']===obj[0]) // true

    let obj = {}

    obj.proto = 5

    console.log(typeof obj['proto']) object

  • 属性存在性

    let obj = {}

    console.log('name' in obj) // 使用in

  • key

    let obj = {

    0:'yayxs',

    'name':'name',

    age:18

    }

    for(let key in obj){

    console.log(key)

    }

  • 一个被 const 修饰的对象是 可以 被修改。

const info = {

username:'yayxs',

age:20

}

info['age'] = 18 ;

console.log(info)

  • 深拷贝 和 浅拷贝

const info = {

username:'yayxs',

age:20,

sex:'nan'

}

let clone = {}

for(let prop in info){

clone[prop] = info[prop]

}

console.log(clone)

delete clone.sex

console.log(clone)

console.log(info)

什么是 深拷贝 :如果一个对象的 属性值 它还是个对象 那把它拷贝一遍 就是深拷贝了

  • 谈一谈垃圾回收机制
    • mark-and-sweep 算法

2020-07-03

  • console.log(123..toString(10))

  • let arr = [ -2.1,1.1, -2.9 , 0 , 1.56 ]
    
    for(let i = 0;i< arr.length;i++ ){
      let floor =  Math.floor(arr[i])
      console.log(floor)
    }
    
    
  • let num = 1234.2345565778
    let rNum = +num.toFixed(5)
    console.log(rNum)
    
  • 经典的面试题 0.1 0.2 0.3

  • Object.is()

  • let arr = [ '\u00A9' , '\u{20331}' , '\u{1F60D}']
    for(let i = 0 ;i<arr.length;i++){
      console.log(arr[i])
    }
    
  • 遍历字符串

  • let str = `my name is yayxs`
    for(let char of str){
      console.log(char)
    }
    
  • 字符串

    • 查找子串

      let stream = `rtmp:123.23.42.12/dsaw`
      let stream1 = `https://yayxs.github.m3u8`
      
      console.log(stream.indexOf('tmp'))
      console.log(stream1.includes('3u8'))
      
    • 获取子串

    let str = `https://yayxs.github.io/`
    
    console.log(str.slice(5,8)) ://
    
  • 经典的面试题 数组中的哪些方法能够改变原数组

    • let arr = ['前','中','后']
      let res = arr.push('第三者') 
      
      console.log(arr) //  ["前", "中", "后", "第三者"]
      console.log(res) // 4
      
    • let arr = ['前','中','后']
      let res = arr.pop('第三者') 
      
      console.log(arr) 
      console.log(res) 
      
    • 遍历数组

    • let arr = ['前','中','后']
      for(let val of arr){
        console.log(val)
      }
      

【umi-nest】基于umi-nest 实现CRUD 后台管理 以及 JWT 登录验证

准备工作

首先要做的就是初始化项目,第一步要做的就是在你的电脑环境下需要安装nestjs 的脚手架

$ npm i -g @nestjs/cli
$ nest new nest-api-service

当我们执行nest new 你的项目名称 之后,我们就创建了一个基于nest 的后端项目

nest -h

和其他的脚手架一样,我们可以通过nest -h ,查看脚手架的帮助命令

name alias desc
controller co 控制器
guard gu 守卫
interface interface 接口
middleware mi 中间件
module mo 模块
pipe pi 管道
service s 服务
library lib 服务

分析文件

我们首先要关注的就是我们的main.ts 文件,这个使我们的跟入口

 const app = await NestFactory.create(AppModule);

大体意思是,我们利用一个nest 提供的工厂方法 来创建一个APP

await app.listen(3000);

然后监听3000 端口

我们打算先写一个接口 post.interface.ts ,规范化我们的数据,然后默认导出

export interface IPost {
  title: string; // 标题
  subTitle: string; // 副标题
  avatar: string; // 头像
  mainContent: string; // 主要内容
  author: string; // 作者
  status: string; // 完成度
}

一般情况下,我们是在posts.service 中服务中对数据的操作

@Controller('posts')
export class PostsController {
    @Get()
    findAll(@Req() request: Request): string {
        console.log(request)
      return `${request.query}`;
    }
}

https://juejin.im/post/5e8ada5a51882573b86f8a72

ngnix 3000端口代理至80

tsrcc→ class component skeleton
tsrcfull→ class component skeleton with Props, State, and constructor
tsrcjc→ class component skeleton without import and default export lines
tsrpcc→ class purecomponent skeleton
tsrpcjc→ class purecomponent without import and default export lines
tsrpfc pure function component skeleton
tsdrpfc stateless functional component with default export
tsrsfc stateless functional component
conc→ class default constructor with props and context
cwm→ componentWillMount method
ren→ render method
cdm→ componentDidMount method
cwrp→ componentWillReceiveProps method
scu→ shouldComponentUpdate method
cwu→ componentWillUpdate method
cdu→ componentDidUpdate method
cwum→ componentWillUnmount method
gdsfp→ getDerivedStateFromProps method
gsbu getSnapshotBeforeUpdate method
sst→ this.setState with object as parameter
bnd→ binds the this of method inside the constructor
met→ simple method
tscntr→ react redux container skeleton
imt create a import

https://ninghao.net/blog/7361

用户的注册

【vue3、vue-next】Vue Composition API Learning

前言

不知道在哪个瞬间,尤大 发了一条微博,我就知道"大事不妙"。要来了……千呼万唤始出来vue3 就在这个特别的日子发布了。

最核心的一点便是Composition API ,在本文咱们先揭开它的面纱,简单的进行横向对比一下,最后我们将一同构建两个版本:一个使用Composition API,另一个使用基于Options API

Composition API

什么是Composition API

新的api 并不会破坏当前2.x 的代码,甚至可以继续使用,新的方式是为了解决vue2.x 一些限制

  • Evan You:has described the Composition API as a reactive API coupled with the ability to register lifecycle hooks using globally imported functions.

    意思是说,这种新的api 是一种反应性的api,并且具有使用全局导入函数注册生命周期钩子的功能

简言之,vue 3 并没有新增什么新的内容,或者说vue 把一些内部的功能暴露出来,提供给开发者,让我们可以屁颠屁颠的直接在组件中使用。

为什么大费周折改变

一程不变是好事,但在开发中,对于一个问题的解决方式一定要精益求精,不断的寻求更优解,这也是开发者的精神吧,我想

在2.x的版本中,我们使用data watch methods 以及各种生命周期,就像这样

20200430172328

正式因为如此,我们很难通过一个庞大而复杂的文件中读取分组,我们经常会来回的滚动,另一个缺点是,由于在组件中无法直观的拆分,因此逻辑重用变得十分的困难

初始vue3

<template>
  <button @click="increment">
    当前的状态是: {{ state.count }}, 当前的状态乘以2计算得{{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2),
    });

    function increment() {
      state.count++;
    }

    return {
      state,
      increment,
    };
  },
};
</script>

通过一个简单的案例,可以得知一个新的apisetup() , 包括状态也好,点击的事件函数也好都是书写在此函数内。这显得与vue 2.x 有点差异,之前的写法我们会把一些方法写在 methods 中,那么这样就会有一个问题,随着项目越来越大,组件变得越来越长,后期维护的时候,

  • 方法和它依赖的数据状态对应不上,在编辑器里上下跳动的找关联

  • 考虑一个场景:一个小模块(其中有数据和状态)比如说,显示一个数和它的2倍,就是我们上面的例子,这样我们可以把描述这一场景 的逻辑和数据直接提到公共的部分,方便复用

typescript支持

vue 3 可以更好的支持typescript ,我们知道ts 有着较好的类型推断

那么当前有什么问题呢,

  • 2.x 版本的vue 在设计师较少的考虑到了类型推断(使用flow),并且在尝试与ts 配合使用时总是会有点问题
  • vue 依赖this 上下文来访问 属性 也好,方法 也罢,可以说this 在vue中是十分有魔力的

vue-class-component

当前很多开发者正在或即将使用类组件 ,那么在vue 3 实现细节上仍然有很多的不确定性,这变成了一个较为危险的行为

目前的最新api 大多是使用普通的变量和函数,类型较为友好,使得开发者可以尽情的享受类型推断带来的快感

那么,这种class-component 的这种方式我们还可以继续使用吗,目前为止,仍然继续可以使用,但是不推荐

横向对比

当然了我们在对比的时候,不得不拿出,这两张图

让我们构建一个简单的组件,该组件允许用户按下按钮即可添加两个数字。首先,我们将快速回顾如何使用基于options的API来完成此操作。然后,我们将使用Composition API重建相同的组件,以说明差异。

使用Options API构建

 data() {
    return {
      num1: 0,
      num2: 0,
      sum: 0,
    };
  },
  methods: {
    addNumbers() {
      this.sum = parseInt(this.num1) + parseInt(this.num2);
    },
  },

使用基于Vue 2选项的API,我们在data()函数中返回了反应数据对象。然后,该AddNumbers()功能将在methods属性中注册。要从中访问变量data(),必须使用this

现在,this它嵌套在方法内部,因此它应该引用方法的对象而不是整个实例,但不是。Vue正在这里进行幕后工作,以便为我们解决这个问题。通常,这非常简单直观,但是在更复杂的情况下,这种幕后处理可能会导致的意外行为this

这也正是我们上文提到的关于typescript 的支持

使用Composition API构建

在使用vue3 之前,官方已经给我们准备好了vue-cli-plugin-vue-next , 熟悉vue 脚手架的人对于此并不陌生,我们可以通过

vue add vue-next

来在项目里使用vue3 ,这样能够很轻松的把vue 2.x 的项目转换为 vue 3项目

main.js

import { createApp } from 'vue';
import App from './App.vue'

createApp(App).mount('#app')

我们可以看到在挂载的时候是不一样的, 这里的不一样大概指得是用法不一样,

在之前的版本

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

核心API

vue 2.x 我们都知道一些常用的api ,那么在最新版本的vue中(基本大的框架与语法已经成型)会多哪些新的api呢,

疑问:到底vue3 是不是多了新的api

ref

import {ref} from 'vue'
// ……
let num3 = ref(0);
let num4 = ref(0);
let sum1 = ref(0);

所有内容现在都在setup()函数内。模板中需要使用的所有函数或属性都应加入,setup()因为这是将它们返回模板的方式。

  • 在内部setup(),我们使用的反应变量在顶部定义为独立变量,而不是data()函数中的返回对象。

还有一个函数,单独的挂出,并不是写在methods中,现在,我们可以轻松地在组件实例之间重用我们的功能,这将显着提高大型代码库的可读性。还请注意,this不再需要引用变量!

  function addNumbersVue3() {
            sum1.value = parseInt(num3.value) + parseInt(num4.value);
        }

最后,我们将函数和属性返回到模板。

 return {
            num3,
            num4,
            sum1,
            addNumbersVue3
        }

这里的一件事是ref在变量中使用let num1 = ref(0)。这就是我们使变量具有反应性的方式!有两个函数可用于处理状态和反应性:refreactive

特点

  • ref如本示例中所示,采用一个值并返回一个反应性引用对象。该对象具有单个值:.value指向提供的值。
  • ref 可以直接创建,就像我们上文提到的,也可以用来创建computed()

computed

有时候我们需要依赖其他状态,在vue 中是通过计算属性来处理。我们起初的时候,是直接通过一个方法来计算两者之和,这是为了更好的来演示用。接下来我们使用computed()

import { computed } from 'vue'
let sum1 = computed(() => parseInt(num3.value) + parseInt(num4.value));

sum1是一个我们称为“ ref”的对象,因为它用作对其持有的内部值的反应性引用。

reactive

反应性 反应状态 副作用 的意思

import { reactive } from 'vue'

// reactive state
const state = reactive({
  count: 0
})

其中state 便是反应性对象,类似,

data(){
    return {
        count:0
    }
}

watchEffect

根据反应状态重新应用副作用

参考

【状态管理】react中redux、react-redux实操指南

一、Redux

1. readux的概述

通用的状态管理辅助工具,习惯上我们可以结合ReactJs 来使用,在组件化开发过程中,组件的数据状态不得不集中化管理,这也是我们使用Redux的原因之一,是一个数据的容器。习惯上我们称之为js库

2 . 三大原则

  • 单一数据源,唯一的状态仓库
  • state是只读 (派发action)
  • 纯函数执行修改数据的修改 (编写 reducers)

3 . 组成部分

  • state
    • 服务端的数据
    • UI数据
    • app state
  • Action
  • Reducer
  • Store

Action

action顾名思义动作行动 行为,一言以蔽之,它是把数据从应用传到仓库的一个动作,也就是这个数据仓库

  • JS对象

  • 格式

    {
        type:ADD, // type 字段是约定,大家的约定,表示动作的名字,
        index:1,
        content:'这是一点内容'
    }
    • 对象的type字段表示执行的动作;字符串常量
    • index 唯一的ID,主要是一个标识
    • content(或者其他)JS对象什么字段都可以啊,一个字段而已

在实际的开发中我们习惯上是action创建函数

const addAction = (params)=>{
    return {
        type:ADD,
        ...params
    }
}

Reducer

现在我们依旧不说store 这个概念,现在动作有了,但是action它只是描述了一下这个动作,但并不知道咋更新数据,提到数据,我们假使

{
    num:0
}

这个简单的js对象 就是数据

ACTION是个普通的对象;REDUCER是个普通的函数

  • 说普通也不普通,js的函数而已

    • function(a,b){
          console.log(a+b)
      }
    • 但是没那么简单

  • 干净简单,

  • // 默认的数据
    const initData = {
        num:123
    }
    // reducer
    const counterReducer =(state=initData,action)=>{
        
      // 啥也不干,返回传进来的state(此时默认的initData)
       return state
    }
  • 怎么可能啥也不干呢

import { addAction } from "./actions";
// 默认的数据
const initData = {
  num: 123
};
// reducer
const counterReducer = (state = initData, action) => {
  // 判断传入的动作是什么类型
  switch (action.type) {
    case addAction:
      return Object.assign({}, state, {
        ...
      });
    default:
      return state;
  }

  // 啥也不干,返回传进来的state(此时默认的initData)
  //   return state;
};

注意

  • 不能修改传进来的数据
  • 在默认情况下,一定得返回旧的state

Store

  • 这就是那个状态仓库,维持状态
  • getState() 方法获取state
  • 提供 dispatch ()方法发送action
  • 通过subscribe()来注册监听

获取状态

getState()

更新状态

dispatch(action) 

也就是我们说的派发一个动作

注册监听(订阅)

subscribe(listener)

4 . 简单案例

在这个时候,有个问题,前边说的这一切,那我们该怎么来创建这个仓库呢

yarn add redux

这个库里就有方法,也就是我们常说的redux

构建action

import { ADD_TYPE } from './actionTypes'
const addAction = (params)=>{
    return {
        type:ADD_TYPE,
        ...params
    }
}

export {
    addAction
}

构建reducer

import { addAction } from "./actions";
// 默认的数据

// reducer
const counterReducer = (state = {num:123}, action) => {
  // 判断传入的动作是什么类型
  switch (action.type) {
    case addAction:
      return Object.assign({}, state, action);
    default:
      return state;
  }

  // 啥也不干,返回传进来的state(此时默认的initData)
  //   return state;
};

export {
    counterReducer
}

创建store

引入文件

import { createStore } from "redux";
import { counterReducer } from "./reducers";

createStore

const store = createStore(counterReducer);
export default store

派发action

const handleClick = ()=>{
    console.log(`点击了按钮`)
    const action = addAction({num:'456'})
    store.dispatch(action)
}

监听

  useEffect(() => {
        store.subscribe(()=>{
            console.log('-----',store.getState())
        })
    }, [])

订阅状态的变更

const render = ()=>{
    ReactDom.render( <App/>, document.querySelector('#root') ) }
// 上来的时候先渲染一次
render() 
// 订阅变更,每当数据发生的变化的时候,就重新渲染
store.subscribe(render)

小结

通过一个简单的案例,我们知道一个简易的流程:

  1. 首先构建一个action 返回一个对象必须有type属性
  2. 构建reducer 响应action t通过return 把数据传回store
  3. 利用redux这个库来创建一个store 传递写好的reducer
  4. 利用的$store.subscribe() 注册监听
  5. 可以通过store.getState() 取值

二 、React-Redux

那在如上我们使用的redux 这个库看起来是没有问题,但是

  • 首先要导入store
  • 然后注册监听
  • 然后组件销毁的时候,我们取消监听

这一波流的操作在每个组件都要走一遍,显然是十分繁琐和重复的,这就需要看谁能不能帮帮我,这就是react-redux 如果需要把redux整合到react 中来使用就需要react-redux

1. 什么是react-redux

  • redux 官方出品

  • 能够更好的结合react 来管理数据

Provider 组件

  • 包裹在最外层的组件之外,这样可以使所有的子组件都可以拿到state
  • 接收store 作为props 通过context 传递

connect 方法

  • 组件内部获取store 中的state
  • 通过connect 加强

mapStateToProps(state,ownProps)

const mapStateToProps = (state, ownProps) => {
    console.log(state)
    return state
    // return {
    //     prop: state.prop
    // }
}

mapDispathToProps(dispath,ownProps)

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    sendAction: () => {
      dispatch({
        type: "ADD_TYPE",
      });
    },
  };
};

2. 使用

  • 安装相关的依赖

  • 构建store 和readucer

  • Provider组件实现

 <>
      <Provider store = {store}>
        <List></List>
        <Detail></Detail>
      </Provider>
    </>
  • connect

combineReducers

  • 函数,接收一个参数
  • 拆分reducer
import { createStore, combineReducers } from "redux";
// import { counterReducer } from "./reducers";
// import rootReducer from './reducers/index'

import { infoReducer } from "./reducers/infoReducer";
import { listReducer } from "./reducers/listReducer";

const reducer = combineReducers({
  infoReducer,
  listReducer,
});

// 构建store
const store = createStore(reducer);
export default store;

创建组件

  • ComA A组件

    import React, { Component } from "react";
    import { connect } from "react-redux";
    class ComA extends Component {
      handleClick = () => {
        this.props.getInfo();
      };
    
      render() {
        return (
          <div>
            {/* <h3>{this.props.}</h3> */}
            <button onClick={this.handleClick}>获取信息</button>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      console.log(state.infoReducer);
      // return {
      //   prop: state.prop,
      // };
      // return state
      return {
        ...state.infoReducer,
      };
    };
    const mapDispatchToProps = (dispatch, ownProps) => {
      return {
        getInfo: () => {
          const actionCreator = {
            type: "GET_INFO",
          };
    
          dispatch(actionCreator);
        },
      };
    };
    export default connect(mapStateToProps, mapDispatchToProps)(ComA);
    
    
  • ComB

    import React, { Component } from "react";
    import { connect } from "react-redux";
    
    class ComB extends Component {
      handleClick = () => {
        this.props.getList();
      };
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>获取列表</button>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state, ownProps) => {
      console.log(state.listReducer)
      // return state
      return {
        ...state.listReducer
      }
    };
    
    const mapDispatchToProps = (dispatch, ownProps) => {
      return {
        getList: () => {
          const actionCreator = {
            type: "GET_LIST",
          };
          dispatch(actionCreator);
        },
      };
    };
    
    
    export default connect(mapStateToProps, mapDispatchToProps)(ComB);
    
    

  • infoReducer.js

    const info = {
      name: "yayxs",
    };
    
    const infoReducer = (state = {}, action) => {
      switch (action.type) {
        case "GET_INFO":
          return {
            ...info,
          };
    
        default:
          return state;
      }
    };
    
    export {
      infoReducer
    }
  • listReducer

    const listArr = [
      {
        id: 1,
        con: "耳机",
      },
    ];
    
    const listReducer = (state = {}, action) => {
      switch (action.type) {
        case "GET_LIST":
          return {
            listArr: [...listArr],
          };
    
        default:
          return state;
      }
    };
    
    export {
      listReducer
    }

三、Redux-Saga

不管怎么说,如上提及数据流操作只支持同步的操作,实现异步的话就需要中间件

1. 中间件

  • 本身就是一个函数
  • 应用在action 发布出去之后

2 . 概述

  • 用来管理副作用,其中包括像异步操作 ,让副作用的执行更加简单
  • es6的语法,参考阮老师

3. createSagaMiddleware

其中源码是这样的

export default function createSagaMiddleware<C extends object>(options?: SagaMiddlewareOptions<C>): SagaMiddleware<C>

export interface SagaMiddlewareOptions<C extends object = {}> {
  /**
   * Initial value of the saga's context.
   */
  context?: C
  /**
   * If a Saga Monitor is provided, the middleware will deliver monitoring
   * events to the monitor.
   */
  sagaMonitor?: SagaMonitor
  /**
   * If provided, the middleware will call it with uncaught errors from Sagas.
   * useful for sending uncaught exceptions to error tracking services.
   */
  onError?(error: Error, errorInfo: ErrorInfo): void
  /**
   * Allows you to intercept any effect, resolve it on your own and pass to the
   * next middleware.
   */
  effectMiddlewares?: EffectMiddleware[]
}

导入

import createSagaMiddleware from "redux-saga";

构建store

const store = createStore(sagaReducer, {}, applyMiddleware(sagaMiddleware));
  • 第一个参数是reducer
  • 第二个initState
  • 第三个参数:中间件

执行

sagaMiddleware.run(defSage);

4. 案例

saga 的辅助函数

  • takeEvery

  • takeLatest

  • throttle

  • SagaCom

 handleClick = (type) => {
   
    switch (type) {
      case "takeEvery":
        this.props.dispatch({
          type: "takeEvery",
        });
        break;
      case "takeLatest":
        this.props.dispatch({
          type: "takeLatest",
        });
        break;

      case "throttle":
        this.props.dispatch({
          type: "throttle",
        });
        break;

      default:
        break;
    }
  };
  • sages/index.js
import {
  takeEvery,
  takeLatest,
  throttle,
  select,
  call,
} from "redux-saga/effects";

import axios from "axios";
export function* defSage() {
  yield takeEvery("takeEvery", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
  // 最后的一次,取消正在运行中
  yield takeLatest("takeLatest", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
  /**
   * 毫秒值
   */
  yield throttle(0, "throttle", function* () {
    const state = yield select((state) => state.payload);

    const res = yield call(
      axios.post,
      `http://rap2.taobao.org:38080/app/mock/249413/mock-api/v1/users/login`,
      {
        ...state,
      }
    );

    console.log(res);
  });
}

effect 创建器

详细的api 用法可以参考官方文档

  • select
  • call
  • take
  • put

业务流程

获取数据

  • 当页面一加载,然后发送一个获取数据的action
  • reducer 匹配对应的action 如果是一部的action 直接把数据返回
  • 在saga 里使用 takeEvery 来进行监听
  • call方法调用异步请求,传入请求的参数
  • put副作用发送action 成功或者是失败
  • 在reducer 里处理action

生命周期

  • componentDidMount获取数据
  • componentWillUpdate 处理数据

四、Redux-Thunk

五、redux 原理

六、思考

  1. Hooks API ,也就是函数式的组件怎么监听页面数据的变化 ,然后执行刷新?
  2. redux-saga 中的辅助函数 takeEvery takeLatest throttle 在底层有什么区别?

【状态管理】解析在 Flutter 开发中会使用到的状态管理库——provider

解析在 Flutter 开发中会使用到的状态管理库


序言

任何人事物都有状态,水结冰、天起大雾、人生气这些何尝不是状态呢?那么对于程序而言,对于语言来说,他们的状态又是什么呢?我们知道前端有那么几个大的框架 Vue.js,React.js,包括但不限于小程序语法、移动端,他们都会有自己活跃的生态环境,同时也会衍生出自己的状态管理工具,像 Vuex,像 Vue 的男朋友一样,形影不离,像 Mobx Redux,好像他们无处不在,为什么说状态管理是那么的重要呢,有时候不用状态管理,简简单单、单单纯纯不就挺好的。虽然话是这么说,可是真正在企业项目的时候,大家又都说组件化开发,组件化开发,那就造成了一个问题,这个数据,这个值怎么办,传来传去不怕传丢了,那这段路让我们一起来聊聊状态管理在 Flutter 中又是什么的一个角色,谁和谁又是“情敌”呢
写分享第一点要考虑的就是起名字,就是一段视频 ,封面到底怎么样才能够吸引人,文章也不例外,一个大家 都乐于去阅读的多是那些名字听起来就炸天的,比如状态管理看这篇就够了Flutter 状态管理终极秘籍等等,当然咱们也不愿去做震惊派,所以暂且称为Flutter 状态管理一锅端 灵感是来自之前看过的一篇文章,毕竟人家写的也是真的很好,希望能趁上这个名字。闲扯了那么多,还是没有一点技术性在,那开始吧……

  • 文章字数
  • 阅读建议

第一章 走进官方推荐的解决方案 Provider

在开始写之前,我决定从官方文档找切入,虽然一些中文的文档写的也很好,咱们从https://flutter.dev/看会不会发现什么蛛丝马迹
120601.png
我么发现官网还真的有关于状态的描述

120602.png

映入眼帘的便是这个小动画,大致意思讲的是添加购物车的 demo,也就是说探索 Flutter 时,有时需要跨应用程序在屏幕之间共享应用程序状态。当然我们也必须考虑很多问题
120603.gif
那对于从事 Ios 或者安卓的开发者来说,真的需要从新的角度去看问题嘛,对于前端开发者来说早已司空见惯。
可以从头开始重建 UI 的某些部分,而不用对其进行修改。Flutter 的速度足够快,即使需要时也可以在每一帧上执行。还有说道UI = F(state)

1.1 了解状态

短时 (ephemeral) 状态

不需要使用状态管理架构(例如 ScopedModel, Redux)去管理这种状态。你需要用的只是一个 StatefulWidget。
在下方你可以看到一个底部导航栏中当前被选中的项目是如何被被保存在 _MyHomepageState 类的 _index 变量中。

class MyHomepage extends StatefulWidget {
  @override
  _MyHomepageState createState() => _MyHomepageState();
}

class _MyHomepageState extends State<MyHomepage> {
  int _index = 0; // 这个index便是短时的,别的地方也不需要去访问它不是吗

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      currentIndex: _index,
      onTap: (newIndex) {
        setState(() {
          _index = newIndex;
        });
      },
      // ... items ...
    );
  }
}

应用状态

如果你想在你的应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态)。什么意思呢,就是说一个状态在 A 页面也要用在 B 页面也要使用,就像一个渣男一样,和谁都有染
03.png

也就是说哪位女士都有点状态上的联系
在这里

Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.

Sometimes you'll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local).

The rule of thumb is: do whatever is less awkward.

翻译过来就是

将 React 用于短暂状态,该状态对应用程序全局无关紧要,并且不会以复杂的方式进行更改。 例如,在某些 UI 元素中切换,即表单输入状态。 将 Redux 用于全局重要的状态或以复杂方式突变的状态。 例如,缓存的用户或后期草稿。

有时,您可能希望从 Redux 状态转换为 React 状态(当在 Redux 中存储某些内容变得笨拙时),或者反过来(当更多组件需要访问某些曾经是本地的状态时)。

经验法则是:做些不太尴尬的事情。

大致就是这些意思

2 开始实践

从官方看到,这也印证了一点,它是官方建议使用的状态管理工具
首先咱们先建立几个互不相干的文件来一下

  • MyPageA
import 'package:flutter/material.dart';

class MyPageA extends StatefulWidget {
  MyPageA({Key key}) : super(key: key);

  @override
  _MyPageAState createState() => _MyPageAState();
}

class _MyPageAState extends State<MyPageA> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Text('我是A页面'),
        ),
      ),
    );
  }
}
  • MyPageB
  • MyPageC
    后两个同样的道理。那么在首页呢咱们写三个按钮,分别可以跳向不同的按钮,在这里为了演示案例,咱们就不做代码的封装等问题。
   child: Center(
          child: Column(
            children: <Widget>[
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳转到A页面的按钮'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳转到B页面的按钮'),
              ),
              RaisedButton(
                onPressed: () {},
                child: Text('我是跳转到C页面的按钮'),
              )
            ],
          ),
        ),

现在期望是这样子的我们在页面 A 做测试

  child: Column(
                children: <Widget>[
                  Container(
                    child: RaisedButton(
                      onPressed: () {},
                      child: Text('+'),
                    ),
                  ),
                  Container(
                    child: Text('现在的值是'),
                  ),
                ],
              )),

想必已经知道要做个什么效果了就是没点击一次的时候,默认值就加一
04.png

  Container(
                    child: RaisedButton(
                      onPressed: () {
                        setState(() {
                          count = count + 1;
                        });
                      },
                      child: Text('+'),
                    ),
                  ),
                  Container(
                    child: Text('现在的值是$count'),
                  ),

现在已经可以实现了,那么我们要在首页也显示这个值怎么办呢,这相当于是他的父亲也想看的值,就像是,儿子过年的时候收了一大笔压岁钱,此时长辈期望知道咱收了多少压岁钱然后,没收好伐
这样子吧,父亲给个方法过来,也就是给个袋子过来,我把压岁钱装进去

 void callback() {
    print('我是首页的方法');
  }

在跳转的时候,把方法传过去,Flutter 中可以任意的传递他们

 Navigator.push(
                    context,
                    new MaterialPageRoute(
                        builder: (context) => MyPageA(myCallBack)),
                  );

可是会有一个问题,但是对于全局应用状态来说你需要在不同的地方进行修改,可能需要大量传递回调函数
写到这儿,忽然有个很有意思的想法,那就是咱们脑海中有个这个想法,
05.png

  • 父亲:父组件即是 main.dart 文件
  • 大儿子:子组件一也就是 MyPageA 文件
  • 二女儿:子组件二 MyPageB 文件
  • 老小:子组件三 MyPageC 文件
    目前是这样的一个情况
 void myCallBack(val) {
    print('我是父亲,这是我的收钱袋子,从孩子哪里没收的压岁钱是$val');
  }
 onPressed: () {
                        setState(() {
                          count = count + 1;
                        });
                        // 调用回调
                        widget.callback(count);
                      },

那这样就会有一个问题,在开发的过程中,就会出现大量的回调函数,这里就引用官方推荐的一种方式,provider package
需要理解 3 个概念

  • ChangeNotifier
    这个是 Flutter SDK 里用来向大家发送通知的一个类,类似于村里的喇叭 用来通知村民一些事情的变化等
    那么就像如上提到的压碎钱的案例,怎们才能把压碎钱在父亲和几个孩子之间进行来回的传递呢,接下来我打算用一下这个类似于广播的东西。
    lib下新建一个文件夹命名provider lib/provider/money_provider.dart
import 'package:flutter/material.dart';

// MoneyProvider 这个类继承自发布者
class MoneyProvider extends ChangeNotifier {
  /// 这里就不说是数据了咱们暂且称为私有数据,并_开头命名
  num _money = 0;
  //  把数据get 一下,类似于暴露
  num get money => _money;
  // 定义一个没有返回值的方法,主要是用来增加自己的压岁钱并展示给其他家里人
  void addMoneyAndShowOthers() {
    _money = _money + 1;
    // 该调用告诉正在侦听此模型的小部件进行重建。
    notifyListeners();
  }
}
  • ChangeNotifierProvider
    ChangeNotifierProvider widget 可以向其子孙节点暴露一个 ChangeNotifier 实例。它属于 provider package。
    那我们喇叭已经搞好了,现在就是要高高挂起。放在所有村民都能够听的地方
    这也就是说hangeNotifierProvider 要放在使用它的部件之上,但是又不可以放的太上,在这个案例中,我们暂且放在
    Remove Link
    121401.png
    这里需要注意的是provider 3.2.0
  • 提供者不赞成使用的“构建器”,而赞成“创建”
  • 不赞成使用代理提供程序的“ builder” /“ initialBuilder”,而分别建议使用“ create”和“ update”
    也就是说 builder 已经是不建议使用了,那么这时候就需要使用新的 api
void main() => runApp(
      ChangeNotifierProvider(
        // builder: (context) => MoneyProvider(),
        create: (context) => MoneyProvider(),
        child: MyApp(),
      ),
    );

当然了,当我们尝试需要多个共享的状态的时候呢,比如压岁钱状态、孩子有没有男女朋友状态等
就可以使用这种方式

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider( create: (context) => MoneyProvider()),
        Provider( create: (context) => MoneyProvider(),),
      ],
      child: MyApp(),
    ),
  );
}
  • Consumer
    如上村长大喇叭都已经就绪了,接下来就是村民应该做的事儿了,就是一条通知发出,广大的村民该怎么接收呢,这里就需要一个部件Consumer我们可以把它想象成一个Container是一样的道理,只不过它不单单的提供布局功能
return Consumer<MoneyProvider>(
  builder: (context, state, child) {
    return Text("${state}是什么呢?");
  },
);

那这个时候,咱们就去二女儿页面去监听一下试试,那这个时候可能需要咱们把有状态的部件更改为无状态的部件即StatelessWidget

  return Container(
      child: Consumer<MoneyProvider>(
        builder: (context, state, child) {
          return Text("");
        },
      ),
    );

我们简单的来看一下这个代码段

  • MoneyProvider 我们必须指定要访问的模型类型(是哪个 provider 类)。在这个示例中,我们要访问 MoneyProvider 那么就写上 Consumer
  • builder 当 ChangeNotifier 发生变化的时候会调用 builder 这个函数
  • context 在每个 build 方法中都能找到这个参数。
  • state 也就是第 2 个参数 ChangeNotifier 的实例
  • child 用于优化目的
    121402

显然我们是成功了的,目前已经可以显示状态数据0

  • Provider.of

有时候就像这样,我们可能是单独的想读出来状态的数据,还不想让压岁钱加 1 呢,也就是说有的时候你不需要模型中的 数据 来改变 UI,但是你可能还是需要访问该数据。
这个时候

Provider.of<MoneyProvider>(context, listen: false)

就像这个样子

Container(
            child: Column(
          children: <Widget>[
            Text('$moneyFromState'),
            Consumer<MoneyProvider>(
              builder: (context, state, child) {
                return Column(
                  children: <Widget>[
                    RaisedButton(
                      onPressed: () {
                        state.addMoneyAndShowOthers();
                      },
                      child: Text('我是一个按钮'),
                    ),
                    Text('${state.money}')
                  ],
                );
              },
            ),
          ],
        ))

最后看一下完整的效果吧
121402.png

本章总结

这篇provider相关的分享其实是在1206号左右就开始写了,由于种种原因到今天才写了差不多,在工作中也有用到状态管理,方案也是用的provider,总体使用下来尤其是和后台通信结合起来个人并没觉得很方便,反而是麻烦了点,可能是项目比较小的原因,所以说还是那句话不近视的话呢,先可以不要戴眼镜。由于平时开发任务也比较重,加上自己也在维护一个全栈的项目。所以这个系列

还是会不断的更新,目前 github 也有几颗星了,下一篇章打算写一下 Model 类的转换,希望看到这儿的你能够多多给个鼓励,希望你也有点收获,盼望每个人都能过上儿时梦想搬的生活
这篇分享的相关代码会同步到 github


-- End but thank you --

【状态管理】真实开发场景中的vuex总是刷新就不在

首先说一下,热重载 这个 由于在开发中较少的使用,请直接移步官网 vuex-热重载

热重载

大体意思是说使用webpack 的模块来进行模块的热重载,

 module.hot.accept(['./mutations', './modules/a'], () => {
    // 获取更新后的模块
    // 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
    const newMutations = require('./mutations').default
    const newModuleA = require('./modules/a').default
    // 加载新模块
    store.hotUpdate({
      mutations: newMutations,
      modules: {
        a: newModuleA
      }
    })
  })

以上代码,见官网,(不得不承认vue生态圈的文档写的是很贴心,这也是为什么youda说整理vue新版本的一系列文档还需要一段时间)。笔者扒了扒我们的公司的陈年项目 并没有找到热重载这块,那就不重点说

测试

大厂一般是要求书写测试用例,以下几种方案可以让我们来进行vuex 的测试

  • node 运行时中进行测试
  • 通过在浏览器中装一些 loader
  • 等等或者其他的方案

表单

我们都知道,存在vuex 中的状态数据是不能直接修改的,这就衍生出了一道面试题

双向绑定和 vuex 是否冲突

这道面试题,你也可以在我之前分享的一篇文章中找到【洋小洋同学】| 五一快乐,哥哥姐姐们,几道小面试题也就一万多字 !

其实官网也给了我们比较优雅的方案 表单处理

其实在这里的时候,我有时在想,大佬之所以是是大佬,他们脚踏实地,好高骛远不太好。在技术这块,官网(不管是英文的也好或者中文文档) 已经明确说明一些问题的答案,我们重视忙于业务,而走的太快

开发场景下

开发环境下 我们可以尝试开启 严格模式 这也是很有必要的,用来调试跟踪

export default new Vuex.Store({
  state,
  mutations,
  actions,
  modules: {},
  strict: process.env.NODE_ENV !== 'production', // 生产环境不要使用严格模式
  // plugins: [createPersistedState()],
});

react牵手nestjs打造帖子后台管理

写作真的是一件很酷的事情,这次就来分享一个小demo。

UI 设计

后端接口

技术选型

太可怕,我想写这篇文章估计自己都不记得多长时间了,也不知在忙啥

阅读文档

更新依赖

更新一下依赖 yarn upgrade-interactive [--latest]

yarn upgrade-interactive [--latest]

完事,全部更新最新

选型介绍

内置并完全支持TypeScript 大体来看可以使用ts

  • OOP
  • FP
  • FRP

脚手架安装

在设备全局安装脚手架,使用 npm i -g @nestjs/cli,或者使用yarn cnpm 道理相似,在此略过

从上图可以看出来当前的版本是V6+ ,截止20200108 也是最新的版本

$ npm i -g @nestjs/cli
$ nest new nest-post-api // nest-post-api 为项目的名字

项目初始化

接下来我们要做的就是利用脚手架创建一个船新的项目

  • Which package manager would you ❤️ to use?
    使用npm 或者yarn 都可以

    项目初始化完成之后,就是这个样子

项目结构

├── index.html
├── main.js
├── test
│   ├── jest-e2e.json
│   └── app.e2e-spec.ts
└── src
    ├── main.ts                 # 
    ├── aapp.service.ts         # 
    ├── app.module.ts           # 
    ├── app.controller.spec.ts
    └── app.controller.ts
    

还记得我们起的端口,那么就先把项目跑起来看看吧

当然了,在相关文档会有更详细的描述

接口文档

一套简明扼要的接口 其中提供出的文档是必要,可以通过OpenAPI(Swagger) 只需要main.ts 中配置就可以了。

 const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe())
  const options = new DocumentBuilder()
    .setTitle('基于nest.js简易帖子API')
    .setDescription('nest.js')
    .setVersion('1.0')
    .addTag('react-antd-posts-admin-service')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api-docs', app, document);

在浏览器地址栏输入 http://localhost:3000/api-docs/

着手开发

由于是制作简单的接口,官网不涉及的api感兴趣的可以参考上文提到的文档

连接数据库

@Module({
  imports: [
    TypegooseModule.forRoot("mongodb://127.0.0.1:27017/nest-blog-api", {
      useNewUrlParser: true
    }),
    
    PostsModule],
  controllers: [AppController],
  providers: [AppService],
})

熟悉脚手架

一般的脚手架都是可以看下,我们可以有什么命令,快捷的命令nest -h

这里体现的是,通过这些命令创建一些控制器等,这里可以参考下官方文档
例如

nest g mo posts
nest g co post

写一个简单的接口

import { Controller, Get } from '@nestjs/common';

@Controller('posts')
export class PostsController {
  @Get()
  index() {
  // 返回的数据
    return [{ id: 1 }, { id: 1 }, { id: 1 }, { id: 1 }];
  }
}

模型Module

  • 跟模块

每个应用程序至少有一个模块,一个根模块。根模块是Nest用来构建应用程序图的起点-Nest 用来解析模块和提供者关系以及依赖关系的内部数据结构。

providers Nest注入器将实例化的提供程序,并且至少可以在此模块之间共享这些提供程序
controllers 必须实例化此模块中定义的一组控制器
imports 导出该模块所需的提供程序的导入模块的列表
exports providers该模块提供的子集,并且应该在导入该模块的其他模块中可用
imports: [
    TypegooseModule.forRoot('mongodb://127.0.0.1:27017/nest-blog-api', {
      useNewUrlParser: true,
    }),
    PostsModule,
  ],

在如上的文件,做了两件事:(1)连接数据库(2)导入帖子模块

控制器Controller

 createPost(
    @Body('title') title: string,

    @Body('subTitle') subTitle: string,
    @Body('avatar') avatar: string,
    @Body('mainContent') mainContent: string,
  ) {
    return this.postsService.createPost(title, subTitle, avatar, mainContent);
  }

服务Service

createPost(title:string,subTitle:string,avatar:string,mainContent:string){
      const post:PostModel = {
        id:uuidv4(),
        title,
        subTitle,
        avatar,
        mainContent,
        status:writingStatus.NotStarted
      }

      this.postList.push(post)

      return post
  }

数据model

// 定义接口
interface PostModel {
  id: string;
  title: string;
  subTitle: string;
  avatar: string;
  mainContent: string;
  status: Status;
}
// 枚举帖子的写作状态
enum Status {
  ING = 'ING', // 正在进行
  NotStarted = 'NotStarted', // 没开始
  END = 'END', // 结束
}

20200411105237*

DTO

核心代码

import { Controller, Get, Post, Body, Delete, Put, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
import { InjectModel } from 'nestjs-typegoose';
import { Post as PostSchema } from './post.model'
import { ModelType } from '@typegoose/typegoose/lib/types';
class CreatePostDto {
    @ApiProperty({
        description: '帖子标题',
        example: "XXXXXXX"
    })
    @IsNotEmpty({ message: "标题不能够是空" })
    title: string
    @ApiProperty({
        description: '帖子内容',
        example: "OOOOOO"
    })
    con: string
}


@Controller('posts')
@ApiTags('博客贴')
export class PostsController {
    constructor(@InjectModel(PostSchema) private readonly postModel: ModelType<PostSchema>) { }

    @ApiOperation({
        summary: '获取帖子列表'
    })
    @Get()
    async index() {
        return await this.postModel.find()
    }
    // 帖子详情
    @ApiOperation({
        summary: '帖子 详情'
    })
    @Get(':id')
    async detail(@Param('id') id: string) {

        return await this.postModel.findById(id)

    }
    // 创建帖子
    @ApiOperation({
        summary: '创建帖子'
    })
    @Post()
    async   createPost(@Body() createPostDto: CreatePostDto) {
        await this.postModel.create(createPostDto)
        return {
            code: 0,
            success: true
        }
    }
    // 更新帖子
    @ApiOperation({
        summary: '更新帖子'
    })
    @Put(':id')
    async update(@Param('id') id: string, @Body() updatePostDto: CreatePostDto) {
        await this.postModel.findByIdAndUpdate(id, updatePostDto)
        return {
            success: true,
            id: id
        }
    }
    // 删除帖子
    @ApiOperation({
        summary: '删除帖子'
    })
    @Delete(':id')
    async remove(@Param('id') id: string) {
        await this.postModel.findByIdAndDelete(id)
        return {
            success: true,
            msg: '删除成功'
        }
    }
}

【前端知识分享】

参考答案在这儿

前言

这次分享的前端相关的小知识点:整体分为几大类

  • 前端浏览器相关

Q1: 浏览器到底是怎么渲染的映射为前端常见的面试题是:当用户心情愉悦的打开浏览器时(输入URL) 发生了什么


  • 框架-Vue相关

Q1: 在Vue框架中,为什么尽量不要修改props

Q2: 如果不小心更改了,vue是怎么做的

Q3: vue的双向绑定和vuex是否冲突

Q4: vue的父组件和子组件的生命周期钩子执行的顺序


  • 原生JavaScript相关

Q1:使用sort() 对数组进行排序 - 【3,15,8,29,102,22】

Q2: 输出代码看结果 为什么

var obj = {
    '2'3,
    '3'4,
    'length'2,
    'splice'Array.prototype.splice,
    'push'Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

  • ES6新语法特性 - 箭头函数

Q1:箭头函数与普通函数(function)的区别是什么?

Q2: 构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么


  • API性能

Q1:a.b.c.da['b']['c']['d'],哪个性能更高?

Q2:数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少


  • 算法相关

Q1: 已知数据格式,实现一个函数 fn 找出链条中所有的父级 id

准备

  • vue 的源码
yarn add vue
  • 一个测试小项目

【微前端microfrontends】什么是微前端?

微前端

我们可能听说过微服务 但到目前为止前端已经不仅仅是前端 ,我们接下来要探讨的就是微前端 (Micro Frontends)。

什么是微前端

历史

在前端的前一阶段,我们还是仅仅切图 然后后端进行套页面。慢慢地慢慢地我们开始进行前后端分离。慢慢的出现三大框架,也是主流的框架。前端还得会PS

那在2016 年时候,被提出,也是和微服务概念 有异曲同工之妙

概念

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently.

我们可以称:微前端是一种方案,或者策略方法技术。我们可以实现由不同的团队 不同的技术 构建统一化的现代web应用程序

核心**

  • 技术无关
  • 代码隔离
  • 建立一定的前缀(这里指的是每个小项目的命名空间)

目前项目问题以及期待

问题

一个是路由的问题,一般的TOC 的项目,页面少的话也还说,但是像100+的页面即使是异步加载,也会性能会受到影响

一个就是技术栈的问题,一个小组可能喜欢React 一个小组就是喜欢写Vue ,跨前端技术栈不一样

  • 上线慢、频繁
  • 框架无法调整
  • 工厂巨大理解困难
  • 工作混乱,规范和灵活相互矛盾
  • 遇到bug 大海捞针
  • 巨石应用

优势与挑战

即使我们不在自己的项目实践微前端,但是在面试 或者一些其他的场景中,难免会提及,就像是TypeScript 一样,凡是流行的,就是大家关注的。我们可能会想得到这样一个效果

  • 服务发现

  • 运行隔离

  • 环境一致

  • 能够增量的方式升级,更新甚至重写前端的代码

    • 大型的前端项目一定会超过几年的迭代。我们还时不时的为客户提供新功能,这样不会影响整体
  • 独立部署

    • 微前端的独立部署能力也是十分关键的,正是因为有独立部署的能力,就降低了无关的风险。不管代码托管到什么位置
    • 我们应该能够在不考虑其他代码库的情况下测试部署等

    image-20200611222028308

那么既然有许多优势,我们实际操作起来又会遇到什么问题呢?比如说像是js代码的隔离 css样式的冲突 按需加载资源 。一些冲突我们可想而知,是再所难免的。为什么这样说,因为一个简单的项目由不同的开发人员参与难免还会出现命名的冲突。

我们会在一些大型成熟的开发框架中,看到渐进式 为什么大家大家那么看重。但在实际的操作中,总是找不到一个合适的地方将功能集成到现有的代码中。我们可以想一想自己日常工作的项目中,是不是具有强耦合。正是因为项目的复杂性让我们每个人都在相互踩脚。不够“干净” 。那面向B端的客户,更会在不同的场景提出不同的需求,开发人员改起来代码瑟瑟发抖

image-20200611223722961

方案思路

建立整合的时间

我们想到的一种方法是将每一个微前端发布一个单独的包,那我们的package.json

{
  "name": "@root/container",
  "version": "1.0.0",
  "description": "A weiqianduan web app",
  "dependencies": {
    "@root/part1": "^1.2.3",
    "@root/part2": "^4.5.6",
    "@root/part3": "^7.8.9"
  }
}

但是不建议这种思路,我们应该找到一种在运行时,并不是在构建时集成的微前端方法

不起眼的iframe

没错我们可以通过iframe 将应用程序在“浏览器” 中组合在一起,一方面可以轻松的实现子页面的独立;另一方面样式和全局变量互不干扰。并且还有很高的隔离度

相关链接

【Nginx】前端也应晓得的一些Nginx命令及配置

背景

关于前端的话,我们与服务器打交道的机会也是很少的,一般企业公司 都会有专门的运维同学 各司其职。

  • 那么后端(Java等):他们一般情况下要做的就是接口服务的支持
  • 那么前端(Web等):咱们主要做的就是写写页面 调调接口

那是不是我们就不需要关注Linux 常用的命令,或者与我无关

场景

那前端切图仔在实际的开发中有没有机会去玩玩运维相关的玩意,是有的

  • 比如说,有这么一个场景,我们利用uni-app 或者三方框架,牵扯到微信分享,这时候我们就需要自己上上传到公司的服务器,比如

    像一些分享的其实是h5 的页面,这时候你就可能自己更新你们公司存放h5 页面资源的文件(用xftp 就行)

  • 还有一种场景,就是,前端大平台项目,像(Vue admin) 等等,你build之后是要更新一下的

  • 接着就是自己开发的项目,练手项目想要自己部署等等,这就更需要了解常用的配置,或者命令

关键词

以后每一篇的文章多个关键词,主要是 SEO 用

  • nginx 命令
  • nginx 常用配置
  • linux
  • Nginx服务管理命令
  • 部署
  • 项目打包部署

常用命令及操作

安装Nginx 服务器

  • CentOS 环境

    $ sudo yum install epel-release && yum install nginx
  • Ubuntu环境

    $ sudo dnf install nginx 

当然,在不同的场景及环境下,安装的命令大体都是类似的操作,接下来就拿一个举例子

检查版本

  • nginx -v
[root@VM_0_3_centos umi-nest]# nginx -v
nginx version: nginx/1.18.0
[root@VM_0_3_centos umi-nest]# 

其中,这个就是咱们的版本 nginx version: nginx/1.18.0

检查配置语法是否合法

  • nginx -t
[root@VM_0_3_centos umi-nest]# nginx -t
nginx: [emerg] invalid number of arguments in "root" directive in /www/server/panel/vhost/nginx/umi_nest.conf:6
nginx: configuration file /www/server/nginx/conf/nginx.conf test failed

上文,说明我们的配置是有问题的不是吗 failed 至于为什么错误,当然是咱们的配置是有点问题,其实咱们只需要了解基本的配置就像

启动Nginx服务

  • service nginx start

    [root@VM_0_3_centos umi-nest]# service nginx start
    Starting nginx... nginx (pid 7691 7690 6001) already running.

说明咱们的nginx 是正在启动

重启Nginx服务

  • service nginx restart

    [root@VM_0_3_centos umi-nest]# service nginx restart
    Stoping nginx...  done
    Starting nginx...  done
    

查看Nginx服务状态

  • service nginx status

    [root@VM_0_3_centos umi-nest]# service nginx status
    nginx (pid 10352 10351 10350) already running.

重新加载Nginx服务

  • service nginx reload

    [root@VM_0_3_centos umi-nest]# service nginx reload
    Reload service nginx...  done
    
    

停止Nginx服务

  • service nginx stop

命令示例

总结

如若权限不够的话,请**sudo **,例如 sudo nginx -s reload

  • nginx -s
    reload 重新加载配置
    reopen 重启
    stop 停止
    quit 退出
  • 查看进程的命令 ps -ef |grep nginx

    [root@VM_0_3_centos umi-nest]# ps -ef |grep nginx
    root     10350     1  0 22:17 ?        00:00:00 nginx: master process /www/server/nginx/sbin/nginx -c /www/server/nginx/conf/nginx.conf
    www      10773 10350  0 22:20 ?        00:00:00 nginx: worker process
    www      10774 10350  0 22:20 ?        00:00:00 nginx: cache manager process
    root     12631 24792  0 22:32 pts/0    00:00:00 grep --color=auto nginx
    

负载均衡

我们可以通过通过简单的配置实现 小小的负载均衡,我们可以举个例子

upstream tomcats{
    server 192.168.25.148:8080 weight=2;
    server 192.168.25.148:8081;
}

server {
    listen       80;
    server_name  tomcat.test.com;
    location / {
        proxy_pass   http://tomcats;
        index  index.html index.htm;
    }
}

只需要在upstream的server后面添加一个weight即可代表权重。权重越高,分配请求的数量就越多。默认权重是1。也就是当请求过来的时候,会有很多的实例来 均衡

关联阅读

写在最后

  • 关于文中的图片素材有的来自三方网站

  • 关于公众号

    • 关于微信公众的DIY 来自 https://www.fotor.com.cn/

      这个网站DIY是有水印的,我买了会员,你可以用我的(然后你可以自己按照自己的喜欢DIY一下)会员就没有讨厌的会员

    • 这是我们的”组织“ 《前端互鱼》(大大的前端咱们厚厚的说)

    • 你可以扫码,笔者前端技能点分享第一时间更新在 公众号

  • 关于文章的画图工具软件,draw.io

想说的话

有时候这搞一点那搞一点,加上搬家,(主要是自己懒) 差不多已经一个月没有分享了,是有在写的 好吧 25天前,可是前端它更新也太快了

image-20200615211935636

以后好好分享好吧,一块学呗

想分享的资源

文中涉及的 xshell putty xftp6 这些我都放在 公众号里(支持正版!!!)需要的话,直接在公众号里取吧,是有的,直接一键就安装了。没事连 linux 搜搜的。省点时间找工具吧,我都有,准备好了

想分享的歌

最近没有听歌,你呢,你最近在听什么?评论区等你

JavaScript数据结构与算法

[TOC]

栈结构的实现

 function Stack() {
        this.arr = [];
        Stack.prototype.push = function (ele) {
          this.arr.push(ele);
        };
        Stack.prototype.pop = function () {
          return this.arr.pop();
        };
        Stack.prototype.peek = function () {
          return this.arr[this.arr.length - 1];
        };
        Stack.prototype.size = function () {
          return this.arr.length;
        };
        Stack.prototype.toString = function () {
          let str = "";
            for (let i = 0; i < this.arr.length; i++) {
            str += this.arr[i] + " ";
          }
          return str;
        };
      }

栈的操作

  • push
  • pop
  • peek
  • isEmpty
  • size
  • toString

队列

队列的操作

击鼓传花

【前端工程化】git 的一些常规操作命令以及小规范


title: git 的一些常规操作命令以及小规范

[前端厚说] 2020-11-21 更新

文中涉及的代码案例 frontend-thick-talk……

本文线上阅读地址 将会在第一时间更新…… 备用地址

本章节B站视频教程 前端工程化 | 企业开发中的commit规范(代码提交规范)

企业开发中的commit规范

背景

从个人角度来说

首先自己习惯把自己的一些代码案例放在github 上一方面是记录学习,最重要的一点就在在未来的某天看看现在写的代码,在现在去总结过去写的代码,接着自我嘲讽一番。笔者2020年截至11月份已经 提交了 653次 😃,早期 commit 每次都是乱七八糟,随便贴一条。你是否也有同样的问题?每一次commit 都像是回答你女朋友的问题,不知道如何更好的描述自己的这次提交信息

从一个企业开发小组的角度

在实际开发场景中,一个成熟的团队是有一种不言而喻的默契。甚至连commit 信息都能重复。那么团队协作的规范化更是迫在眉睫

  • 规范化
  • 一体化
  • 简约化
  • 性感化

团队协作是如此的美妙。

BUT 真实场景是这样的吗?甚至有的 连 node_modules 都上传上去了。(我是在掘金沸点看到的有孩子这样吐槽)。commit 信息更是写的乱起八遭。那会有什么问题呢?在企业开发的过程中,尤其是

  • 分支较多
  • 项目定制型需求频繁
  • cherry-pick需求

那每当想 cherry-pick 一小块需求的时候,甚至不知道是哪个commit 😠

关键词

commmit commitizen husky commitlint cz-conventional-changelog

  • git commit
  • 规范化提交
  • commitizen
  • husky
  • commitlint
  • cz-conventional-changelog
  • 前端工程化

优秀的开源项目

我们先来看一看一些优秀的开源项目,他们的commit 日志

好了,够了,那么咱们也许可能没有这样完善的项目架构,但是这种规范还是值得学习的。那么这么才能够规范化我们的代码呢,往下看

实操过程

第一步

我们可以尝试在全局安装一个包 commitizen

npm install -g commitizen

第二步

  • 首先全局安装 cz-conventional-changelog
npm install -g cz-conventional-changelog
  • 然后在项目里安装
commitizen init cz-conventional-changelog --save --save-exact

接着你可以看下你项目的package.json ,会多出一部分配置

"devDependencies": {
    "cz-conventional-changelog": "^3.2.0"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }

之后需要 git commit 的操作全部换成 git cz

第三步

项目内安装 commitlint

yarn add @commitlint/config-conventional @commitlint/cli

之后你的 package.json 又会多出一部分的配置

  "dependencies": {
    "@commitlint/cli": "^8.3.5",
    "@commitlint/config-conventional": "^8.3.4"
  }

接着在package.json 统计目录新建 commitlint.config.js 文件 然后写入

module.exports = { extends: ["@commitlint/config-conventional"] };

第四步

项目中安装husky

yarn add husky

接着配置 husky

"dependencies": {
    "@commitlint/cli": "^8.3.5",
    "@commitlint/config-conventional": "^8.3.4",
    "husky": "^4.2.5"
  },
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -e $GIT_PARAMS"
    }
  }

当我们去以不合法的提交信息进行提交代码时,会进行检查

总结

整个提交的流程,大概就是这样的,以下我贴出步骤,至于具体的像以下内容代表的含义,你可以直接,在某搜索引擎,然后,输入上文提到的关键词,应该会有词条告诉你都是啥意思

feat:新功能(feature)
fix:修补bug
docs:文档(documentation)
style: 格式方面的优化
refactor:重构
test:测试
chore:构建过程或辅助工具的变动
$ git cz
cz-cli@4.1.2, cz-conventional-changelog@3.2.0

? Select the type of change that you're committing: docs:     Documentation only changes
? What is the scope of this change (e.g. component or file name): (press enter to skip) format docs ? Write a short, imperative tense description of the change (max 53 chars):
 (11) format docs
? Provide a longer description of the change: (press enter to skip)
 n
? Are there any breaking changes? No
? Does this change affect any open issues? No
\husky > commit-msg (node v12.16.1)
[master 4baa2ce] docs(format docs): format docs
 1 file changed, 16 deletions(-)
 rewrite flutter_state_provider/README.md (100%)

git log --reverse 命令

通过此命令进行倒序展示log 信息,排在第一个的就是仓库的第一次 commit 信息,这也是git 查看第一次 commit的方式,接着通过 git checkout xxx 可以很方便的签出该 commit 的代码。通过此种方式可以更好的 阅读一些 开源的库 或者框架

【实战】2020 基于react生态的一次博客实战探索

2020 基于react生态的一次博客实战探索

  • 后台管理系统 admin系统 cms 系统 对数据增删查改 admin
    • 文章
    • 用户等等
  • 前台展示web 应用 面向于C端 互联网的用户都能看到

暂且先准备这样,后续的接口会慢慢来

使用的技术

  • react技术栈
  • 语言 (语法) 这里我们采用 TypeScript

动态调整

一切都是动态调整的 也是一次我们共同学习的过程

代码

放在github上

开始

blog-fe-cms/

持续的来写

环境变量 十分重要

开发场景下 是需要 读 开发的一些常量配置

生产 又是不同的一套配置

环境变量 env

选择一个什么样的UI框架呢

  • 我们知道, CRA 功能是什么呢 后台管理系统数据的处理

  • react 生态中 UI

  • 社区里优秀的方案

怎么把 antd 集成到我们的项目中?

并不想去给大家直接说怎么做

思考?

部署

/www/server/nginx/conf/nginx.conf synta
events
    {
        use epoll;
        worker_connections 51200;
        multi_accept on;
    }

http
    {
        include       mime.types;
                #include luawaf.conf;

                include proxy.conf;

        default_type  application/octet-stream;

        server_names_hash_bucket_size 512;
        client_header_buffer_size 32k;
        large_client_header_buffers 4 32k;
        client_max_body_size 50m;

        sendfile   on;
        tcp_nopush on;

        keepalive_timeout 60;

        tcp_nodelay on

CSS 预编译器 less

我们是用 hooks语法 新特性

  • 类组件
  • hooks

重点是什么呢?

Hooks

useState

  • 基于 next,js UI ====> 简单的 辅助学习 react hooks
  • 简单的去写hooks 函数 状态

setState

  • 面试 火热 一个题目
    • setState 是同步 的还是异步的呢?

react 的 jsx 中的点击事件 是 看起来像所谓异步

 handleClick() {
    // console.log(this);
    console.log(this.state);
    this.setState({ val: this.state.val + 1 });

    console.log(this.state); // 所谓的 异步
  }
 componentDidMount() {
    console.log(this.state);
    this.setState({ val: this.state.val + 1 });
    console.log(this.state); // 像所谓的异步一样
  }
 timer: ReturnType<typeof setTimeout> = setTimeout(() => {
    console.log(this.state);
    this.setState({ val: this.state.val + 1 }); // 提供给开发者
    console.log(this.state); // 好像是 同步的一样 所谓的异步
  }, 2000);

浅拷贝 深浅拷贝 this 指向 绑定this 的方式又有什么呢 case

关于像 对象 或者是 数组 基本 aPI 实际的而开发 中 深浅拷贝 防抖节流 都会lu

class App extends React.Component {
  state = { val: 0 }
  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val);
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
    }, 0)
  }
  render() {
    return <div>{this.state.val}</div>
  }
}
  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的

  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

useEffect

componentDidMount`, `componentDidUpdate`, and `componentWillUnmount

都有什么生命周期呢 ?? 面试

粒子登录背景

https://github.com/matteobruni/tsparticles

import { ComponentClass } from "react";
     8 | import { Container } from "tsparticles/dist/Core/Container";
  >  9 | import type { IOptions } from "tsparticles/dist/Options/Interfaces/IOptions";
       |             ^
    10 | import type { RecursivePartial } from "tsparticles/dist/Types/RecursivePartial";
    11 | import { IPolygonMaskOptions } from "tsparticles/dist/Plugins/PolygonMask/PolygonMaskPlugin";
    12 | import { IAbsorberOptions } from "tsparticles/dist/Plugins/Absorbers/AbsorbersPlugin";
@commitlint/cli                     8.3.5     8.3.5    9.0.1  blog-fe-cms
@commitlint/config-conventional     8.3.4     8.3.4    9.0.1  blog-fe-cms
@testing-library/jest-dom           4.2.4     4.2.4   5.11.0  blog-fe-cms
@testing-library/react              9.5.0     9.5.0   10.4.3  blog-fe-cms
@testing-library/user-event         7.2.1     7.2.1  12.0.11  blog-fe-cms
@types/jest                        24.9.1    24.9.1   26.0.3  blog-fe-cms
@types/node                      12.12.47  12.12.47  14.0.14  blog-fe-cms
typescript                          3.7.5     3.7.5    3.9.5  blog-fe-cms

更新包依赖

  1. package.json 文件所在的目录中执行 npm update 命令。
  2. 执行 npm outdated 命令。不应该有任何输出。
# npm
npm i --save react@latest
# yarn
yarn add react@latest
npm i -g yarn
npm i -g npm-check
npm-check -u
yarn upgrade-interactive  --latest

less 模块化 css

camsong/blog#5

Warning: Prop `className` did not match. Server: "PrivateSwitchBase-input-8 MuiSwitch-input" Client: "PrivateSwitchBase-input-4 MuiSwitch-input"
    in input (created by ForwardRef(SwitchBase))
    in span (created by ForwardRef(IconButton))
    in span (created by ForwardRef(ButtonBase))
    in ForwardRef(ButtonBase) (created by WithStyles(ForwardRef(ButtonBase)))
    in WithStyles(ForwardRef(ButtonBase)) (created by ForwardRef(IconButton))
    in ForwardRef(IconButton) (created by WithStyles(ForwardRef(IconButton)))
    in WithStyles(ForwardRef(IconButton)) (created by ForwardRef(SwitchBase))
    in ForwardRef(SwitchBase) (created by WithStyles(ForwardRef(SwitchBase)))
    in WithStyles(ForwardRef(SwitchBase)) (created by ForwardRef(Switch))
    in span (created by ForwardRef(Switch))
    in ForwardRef(Switch) (created by WithStyles(ForwardRef(Switch)))
    in WithStyles(ForwardRef(Switch)) (at pages/index.tsx:57)
    in div (created by ForwardRef(Paper))
    in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
    in WithStyles(ForwardRef(Paper)) (created by ForwardRef(Card))
    in ForwardRef(Card) (created by WithStyles(ForwardRef(Card)))
    in WithStyles(ForwardRef(Card)) (at pages/index.tsx:54)
    in div (at pages/index.tsx:53)
    in index (at _app.tsx:31)
    in ThemeProvider (at _app.tsx:28)
    in MyApp
    in ErrorBoundary (created by ReactDevOverlay)
    in ReactDevOverlay (created by Container)
    in Container (created by AppContainer)
    in AppContainer
    in Root
https://github.com/mui-org/material-ui/tree/master/examples/nextjs-with-typescript
https://jsonplaceholder.typicode.com/

api 请求 数据请求 非常重要一部分

面试

像 vue 和 react 都有非常常见的一个问题 就是 key

Each child in a list should have a unique "key" prop

TypeScript 写 React

  • 之前呢 我们的方案 是 基于CRA 写 ts
  • 更换思维 我们现在站在 tS 去考虑react

一方面呢是 继续 走hooks

一方面 中心 是在 ts

接着 写一些 工具呀 或者是通用的方法

还会涉及 面试题

前言

本篇参考 TypeScript and React

我们写react项目的打开方式有多种,那本篇我们将站在TypeScript 的角度逆向分析,我们该怎么去优雅的用ts 描述React,想必你一定会有所收获

版本

开篇我们是需要告知我们的package.json 中一些核心依赖的版本(这在不同的版本也许是不同的效果)

思路

整体思路是依着TypeScript 的基础上 然后构建一个 React 应用,这里参考 ts 以及 webpack 的 官方官方官方文档

准备开始

为了节约大家的时间,首先在阅读下方的文章之前需要 打开几个网站

然后你需要自己跟着 ts 的官网描述搭建一个 webpack + TypeScript + React.js 的初始化项目,也许https://www.typescriptlang.org/docs/handbook/react-&-webpack.html 能帮到你,简陋的 tsconfig.json 文件大致是这样的。不得不提的是

  • 我们可以安装 webpack-cli
  • 可以通过 npx webpack -w 来监听index.html 文件的修改
{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es6",
    "jsx": "react",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "lib": ["dom", "es2015"]
  },
  "include": ["./src/**/*"]
}

文件的导入方式

这里我们可以不使用之前的方式,如下

import * as React from "react";

组件 Components

无状态组件(函数式)

js 环境下,一个简单的Button 组件,

// 绑定元素“children”隐式具有“any”类型。
// 绑定元素“handleClick”隐式具有“any”类型
const Button = ({ onClick: handleClick, children }) => (
  <button onClick={handleClick}>{children}</button>
);

这就需要我们对其进行描述,

type Props = {
  onClick(e: MouseEvent<HTMLElement>): void  // 这里是点击事件的类型 HTMLElement泛型是个ele元素
  children?: ReactNode // 而接收的children 是个react中的 node节点(也就是所谓的Dom或者组件等)
 }

const Button = ({ onClick: handleClick, children }:Props) => (
  <button onClick={handleClick}>{children}</button>
)

还记不记得,在本文的开篇我们一起说了,一些依赖包,那么@types/react 中就替我们声明了一些优雅的描述

type Props = { onClick(e: MouseEvent<HTMLElement>): void };

const Button: SFC<Props> = ({ onClick: handleClick, children }) => (
  <button onClick={handleClick}>{children}</button>
);

也就是说我们可以通过 import React, { SFC} from "react"; 其中 SFC 以及有我们的 传入的组件(这里指 children)

或者是这样的,你也可以看下下面的代码(这里我们讨论下最常见的组件属性传值)

import React from "react";
import PropTypes from "prop-types"; // 引入类型的描述

export function ChildCom({ children, title = "我将从父组件传过来" }) {
  return (
    <div>
      {title}: {children}
    </div>
  );
}

ChildCom.propTypes = {
  title: PropTypes.string,
  children: PropTypes.node.isRequired,
};
import * as React from 'react'

// 定义一个接口用来描述我们即将接收到的属性也好或者是组件节点也好
export interface ChildComProps {
  title?: string // 传递的参数 是可选的
  children: React.ReactNode // ReactNode 这里我们上边提到了
}

export function ChildCom({
  children,
  title = '我将从父组件传过来',
}: ChildComProps) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

我们接着看

export interface ITitleProps {
  title?: string;
}

const MyText = () => {
  return <>副标题</>;
};

/**
 * 此时我们的函数参数  是从泛型  FunctionComponent 推断出来的
 * 虽然看起来和第一个相似 但是 我们可以使用可选的 子组件 children
 * @param
 */
const Header: FunctionComponent<ITitleProps> = ({ title, children }) => {
  return (
    <>
      <h2>{title}</h2>
      {children}
    </>
  );
};

const App = () => {
  return (
    <>
      <hr />
      <Header title="欢迎你">ceshi</Header>
    </>
  );
};

有状态组件(calss 类)

既然是有状态的组件,或者一个开始,我们会想到计数器 时钟 ,因为案例虽小,但是足以说明我们的问题

一个计数器(此处如此美观代码参见https://juejin.im/post/5b07caf16fb9a07aa83f2977

const initialState = { clicksCount: 0 }; // 初始化
type State = Readonly<typeof initialState>; // 这里我们的state是不可直接进行修改,不是吗

/**
 * 这里我们注意:<object,State>
 * 泛型的第一个参数 一般是指的 props (props是对象类型所以咱们可以暂时 object)
 *      第二参数 一般是  状态 state
 */
class Counter extends Component<object, State> {
  readonly state: State = initialState;
  // 不要忘了 render 方法 用来渲染视图
  render() {
    const { clicksCount } = this.state;

    return (
      <>
        <button onClick={this.handleIncrement}>加加加</button>
        <button onClick={this.handleDecrement}>减减减</button>
        <p>你点击了我{clicksCount}</p>
      </>
    );
  }
  private handleIncrement = () => this.setState(incrementClicksCount);
  private handleDecrement = () => this.setState(decrementClicksCount);

}

const incrementClicksCount = (prevState: State) => ({
  clicksCount: prevState.clicksCount + 1,
});
const decrementClicksCount = (prevState: State) => ({
  clicksCount: prevState.clicksCount - 1,
});

一个简单的时钟,用来显示时间

import React, { Component } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

/**
 * 当前时间
 */

export interface ClockState {
  time: Date;
}

/**
 * 通用参数允许我们传入 属性和状态
 */
class App extends Component<{}, { time: Date }> {
  /**
   * 设置当前时间
   */
  setNow() {
    this.setState({
      time: new Date(),
    });
  }
  /**
   * 转换时间
   */
  time2Str(time: Date) {
    return time.toLocaleTimeString();
  }
  // 在组件挂在之前 我们去设置一下时间
  componentWillMount() {
    this.setNow();
  }
  // 在组件挂在后呢 我们需要每一秒更改一下状态
  componentDidMount() {
    setInterval(() => this.setNow(), 1000);
  }
  // 然后 就是渲染 至页面
  render() {
    return (
      <>
        <p>{this.time2Str(this.state.time)}</p>
      </>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("example"));

构造函数

export interface ISimpleProps {}

class Simple extends Component<ISimpleProps, {}> {
  constructor(props: ISimpleProps) {
    super(props);
  }
}

默认属性 defaultProps

  static defaultProps = {
    msg: 'Hello everyone!'
  }

子组件

class Simple extends Component {
  render() {
    return <>123</>;
  }
}

class App extends Component {
  render() {
    return <>{this.props.children}</>;
  }
}

ReactDOM.render(
  <App>
    {" "}
    <Simple />{" "}
  </App>,
  document.getElementById("example")
);

事件

事件是关键

import React, { Component, MouseEvent } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

export class Button extends Component {
  handleClick(event: MouseEvent) {
    console.log(event);
    event.preventDefault();
  }

  render() {
    return <button onClick={this.handleClick}>{this.props.children}</button>;
  }
}
ReactDOM.render(<Button>点击啊</Button>, document.getElementById("example"));

限制性事件处理

可以使用泛型

import React, { Component, MouseEvent } from "react"; // 导入函数组件

import * as ReactDOM from "react-dom";

export class Button extends Component {
  /*
    点击事件限定为  HTMLButton 元素类型
  */
  handleClick(event: MouseEvent<HTMLButtonElement>) {
    console.log(`按钮点击了`);
    event.preventDefault();
  }

  /* 
    泛型可以让我 联合类型 是HTMLButtonElement 或者是 HTMLAnchorElement
  */
  handleAnotherClick(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) {
    event.preventDefault();
    alert("Yeah!");
  }

  render() {
    return <button onClick={this.handleClick}>{this.props.children}</button>;
  }
}

ReactDOM.render(<Button>点击啊</Button>, document.getElementById("example"));

类型校验

/**
 * prop-types 中有个 InferProps
 * @param
 */
function Article({ title, id }: InferProps<typeof Article.propTypes>) {
  return (
    <div className="article">
      <h1>{title}</h1>
      <span>{id}</span>
    </div>
  );
}

Article.propTypes = {
  title: PropTypes.string.isRequired,
  id: PropTypes.number.isRequired,
};
/**
 * 在ts 的环境中  id 是可选的
 */
Article.defaultProps = {
  id: 20,
};

class App extends Component {
  render() {
    return (
      <>
        <Article title="文章" id={1} />
      </>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("example"));

重心是放在处理属性上

function ArticleList({ children }: InferProps<typeof ArticleList.propTypes>) {
  return <div className="list">{children}</div>;
}

ArticleList.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]).isRequired,
};

hooks 用法

import React, { FunctionComponent, useState, FC } from "react";
import * as ReactDOM from "react-dom";

// 组件作为数字初始值
const Counter: FunctionComponent<{ initial?: number }> = ({ initial = 0 }) => {
  // 我们传递了一个数字
  const [clicks, setClicks] = useState(initial);
  return (
    <>
      <p>Clicks: {clicks}</p>
      <button onClick={() => setClicks(clicks + 1)}>+</button>
      <button onClick={() => setClicks(clicks - 1)}>-</button>
    </>
  );
};

const App: FC = () => {
  return (
    <>
      <Counter />
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("example"));

useState & useEffect

import React, { FunctionComponent, useState, FC, useEffect } from "react";
import * as ReactDOM from "react-dom";

const Simple: FC = () => {
  const [name, setName] = useState("yayxs");
  const [width, setWidth] = useState(0);
  useEffect(() => {
    return () => {
      document.title = `Hello ${name}`;
    };
  }, [name]);

  // 事件的派发监听
  useEffect(() => {
    const eventHandler = () => {
      setWidth(Number(window.innerWidth));
    };
    window.addEventListener("resize", eventHandler);
    return () => {
      window.removeEventListener("resize", eventHandler);
    };
  }, [name]);
  return (
    <>
      <h4>{width}</h4>
    </>
  );
};

const App: FC = () => {
  return (
    <>
      <Simple />
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("example"));

useContext

// 上下文中有个字符串类型的 属性
export const lanContext = React.createContext({ lan: "en" });

const Simple: FC = () => {
  const { lan } = useContext(lanContext);
  return (
    <>
      <h4>{lan}</h4>
    </>
  );
};

useRef

function Simple() {
  // 用null 初始化 虽然初始化 是 null 但是 尝试去寻找 HTMLInputElement 类型的元素
  const inputEl = useRef < HTMLInputElement > null;
  const handleClick = () => {
    // 如果存在的话,才使聚焦
    if (inputEl && inputEl.current) {
      inputEl.current.focus();
    }
  };
  return (
    <>
      {/* inputEl也只可与输入元素一起使用 */}
      <input ref={inputEl} type="text" />
      <button onClick={handleClick}>Focus the input</button>
    </>
  );
}

useMemo

/**
 * 我们可以通过使用useEffect 然后传入一些参数来影响函数的执行
 * useMemo做类似的事情。
 * 假设我们有计算量大的方法,并且只想在它们的参数更改时运行它们,
 * 而不是每次组件更新时都运行它们。useMemo返回记忆的结果,并且仅在参数更改时才执行回调函数。
 */

useCallback

useReducer

// 首先是类型定义

type ActionType = {
  type: "reset" | "decrement" | "increment", // 联合类型
};

type StateType = {
  count: number,
};
const initialState = { count: 0 };

function reducer(state: StateType, action: ActionType) {
  // 确保我们正确的设置相关的情况
  switch (action.type) {
    case "reset":
      return initialState;
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter({ initialCount = 0 }) {
  /**
   * 参数一 reducer函数
   * 参数二 初始状态
   */
  const [state, dispatch] = useReducer(reducer, { count: initialCount });
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}

Render props and child render props

Context

/**
 * 上下文API 允许在全局级别共享数据
 *  - provider 一个提供者 提供数据传入到子级
 *  - Consumer 消费者 使用传递来的数据
 */

// 1 上下文 定义类型
type ContextProps = {
  flag: boolean,
  lan: string,
  theme: string,
};

// 1 创建上下文
// export const AppContext =  React.createContext({
//   flag:true,
//   lan:'cn',
//   theme:'dark'
// })
export const AppContext = React.createContext < Partial < ContextProps >> {};
// 2 Provide context

const App = () => {
  return (
    <AppContext.Provider
      value={{
        lan: "de",
        flag: true,
        theme: "light",
      }}
    >
      <Header />
    </AppContext.Provider>
  );
};

// 3 Consume context

const Header = () => {
  return (
    <AppContext.Consumer>
      {({ flag }) => {
        if (flag) {
          return <h1>Logged in!</h1>;
        }
        return <h1>You need to sign in</h1>;
      }}
    </AppContext.Consumer>
  );
};

【读书】《图解HTTP》

  • http/0.9
  • http/1.0
  • http/1.1

http协议

本身是无状态的,后来引入cookie技术,用来管理状态。主要通过在请求响应的报文中写入COOKIE

大体流程

  1. ​ 响应报文内有个叫 Set-Cookie 首部字段信息 通知客户端保存
  2. 发送请求时在请求报文中加入Cookie值
  3. 服务端检查是哪个客户端发的连接请求

三次握手

为了准确无误传送数据,TCP采用三次握手

  • URL 网页地址 是URI的子集
  • URI 资源定位标识符

http方法

在http/1.0和http/1.1支持的方法不同

  • GET
  • POST 传输实体主体
  • PUT 传输文件
  • HEAD 获得报文首部
    • 和GET差不多不返回报文的主体内容
  • DELETE 删除文件 和PUT 相反
  • OPTIONS 询问支持的方法
    • 用来查询针对请求URI指定资源支持的方法
  • TRACE 容易引发跨站追踪
  • CONNECT 要求用隧道协议连接代理

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.