0%

如果说flex适合做一维(水平或竖直放向)元素的布局,那么grid就是做二维布局的,如字面意思,grid布局将平面划分成 m*n 的网格,子元素分布其中,以所处/所占的行和列的控制实现页面layout的划分

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
<div class="wrapper">
<div class="one">One</div>
<div class="two">Two</div>
<div class="three">Three</div>
<div class="four">Four</div>
<div class="five">Five</div>
<div class="six">Six</div>
</div>
<style>
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
grid-auto-rows: minmax(100px, auto);
}
.one {
grid-column: 1 / 3;
grid-row: 1;
}
.two {
grid-column: 2 / 4;
grid-row: 1 / 3;
}
.three {
grid-row: 2 / 5;
grid-column: 1;
}
.four {
grid-column: 3;
grid-row: 3;
}
.five {
grid-column: 2;
grid-row: 4;
}
.six {
grid-column: 3;
grid-row: 4;
}
</style>

display: grid 或 inline-grid区分整个区域是否作为行内元素插入,不影响区域内的grid

列划分:

1
2
3
grid-template-columns: 200px 500px 100px;
grid-template-columns: repeat(4, 25%);
grid-template-columns: 200px repeat(3, 1fr) 100px;

注:fr是grid的特殊单位,可以从总分列去掉固定宽度列后均分剩余列
行划分grid-template-rows类似,也可以按比例划分剩余宽度

间距:

1
2
3
grid-gap: 5px;
grid-row-gap: 5px;
grid-column-gap: 5px;

填充:

1
grid-template-columns: repeat(auto-fill, 200px);

以200px为一列,根据区域宽度调整列数(响应式)

区间:

1
grid-template-columns: 1fr 1fr minmax(300px, 2fr)

第三个列宽最少也是要 300px,但是最大不能大于第一第二列宽的两倍。

区域定义:(略)

流:

1
2
3
grid-auto-flow: row;
grid-auto-flow: row dense;
grid-auto-flow: column;

填充单元格的横纵顺序,如果遇到尺寸不够而挤到下一行的情况,dense可以使用合适的子元素填充前面的空余单元格

对齐:
justify-item, align-item:父容器配置,分别控制子元素在所处单元格空间的对齐方向,两者默认是stretch撑满空间
可选值:start center end stretch

justify-content, align-content:父容器配置,分别控制整个grid(父容器)在上层容器中的对齐方向,两者默认是start

justify-self, align-self:子元素配置,控制自身在所处单元格空间的对齐方向与*-item一致

隐式网格:
在超出grid-template-columns和grid-template-rows的定义之后应用的网格划分

1
2
3
grid-template-columns: 200px 500px 100px;
grid-template-rows: 100px 100px;
grid-auto-rows: 50px;

指定坐标:

1
2
3
4
5
6
.item {
grid-column-start: 3;
grid-column-end: 4;
grid-row-start: 1;
grid-row-end: 4;
}

svg 可缩放矢量图形(Scalable Vector Graphics), 使用xml格式定义图形,大概长这样子

1
2
3
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>

嵌入html中可以作为dom操作,在数据可视化入门中曾提到D3数据可视化库即使用svg进行动态渲染的

对于简单的icon响应可以有

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
<style>
.toggle-btn {
display: inline-block;
width: 40px;
height: 40px;
cursor: pointer;
color: #000;
background: #ccc;
}

.toggle-btn>input {
width: 0;
height: 0;
opacity: 0;
}

.toggle-btn>svg {
width: 100%;
height: 100%;
}

input:checked+svg circle {
fill: blue
}
</style>
<label class="toggle-btn">
<input type="checkbox">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="20" cy="20" r="20" stroke="black" stroke-width="2" fill="none" />
</svg>
</label>

svg-react-loader

将.svg文件资源作为组件载入

1
2
3
import MyIcon from '-!svg-react-loader!../../assets/image/icon.svg'
...
return (<> <MyIcon> <>)

作为资源路径
1
2
3
import MyIcon from '../../assets/image/icon.svg'
...
return (<img src={MyIcon} />)

作为inline element(原生React特性)
1
2
3
4
5
import {ReactComponent as MyIcon} from '../../assets/image/icon.svg'
...
return (<div style={{color:'red', cursor: 'pointer'}}>
<MyIcon />
</div>)

ng-inline-svg

shared.module.ts

1
2
3
4
5
6
7
8
9
10
import { InlineSVGModule } from 'ng-inline-svg'

@NgModule({
imports: [...LibModules,
InlineSVGModule.forRoot()],
exports: [...MuiModules],
declarations: [],
providers: [],
})
export class SharedModule { }

mycomponent.html
1
<div [inlineSVG]="'assets/image/icon.svg'"></div>

鼠标响应

Web MDN: svg pointer-events

async await写一个训练过程

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
async function practise() {
console.log('训练开始...')
let result;
result = await warmup(2)
if(result) console.log('热身完成。。')
result = await squat(5)
if(result) console.log('深蹲完成。。')
result = await boating(3)
if(result) console.log('划船完成。。')
}

function warmup(time){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, time*1000);
});
}

function squat(time){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, time*1000);
});
}

function boating(time){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, time*1000);
});
}

// 执行这个‘训练’
practise().then(res=>{
console.log('完了')
})

从输出结果上看,特点是按照顺序逐步完成某动作,似乎本身就是一个generator
于是把每个动作装到一个generator里
1
2
3
4
5
function* practisePlan() {
yield warmup(2)
yield squat(5)
yield boating(3)
}

yield返回的是一个promise,promise ‘resolve’的情况下才会 ‘next’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let practise = function () {
return new Promise((resolve, reject)=>{
const plan = practisePlan()
Promise.resolve(plan.next().value).then(result=>{
if(result) console.log('热身完成。。')
return plan.next().value;
}).then(result=>{
if(result) console.log('深蹲完成。。')
return plan.next().value;
}).then(result=>{
if(result) console.log('划船完成。。')
resolve() // <--完了
})
})
}

再看循环中的async await的栗子 —-> S-为什么说 async/await是generator的语法糖?

Node.js Streams: Everything you need to know(译文)

在 Node.js 中有四种基本的流类型:Readable(可读流),Writable(可写流),Duplex(双向流),Transform(转换流)。

  • 可读流是数据可以被消费的源的抽象。一个例子就是 fs.createReadStream 方法。
  • 可写流是数据可以被写入目标的抽象。一个例子就是 fs.createWriteStream 方法。
  • 双向流即是可读的也是可写的。一个例子是 TCP socket。
  • 转换流是基于双向流的,可以在读或者写的时候被用来更改或者转换数据。一个例子是 zlib.createGzip 使用 gzip 算法压缩数据。你可以将转换流想象成一个函数,它的输入是可写流,输出是可读流。你或许也听过将转换流成为“通过流(through streams)”。

所有的流都是 EventEmitter 的实例。触发它们的事件可以读或者写入数据,然而,我们可以使用 pipe 方法消费流的数据。

从流到流

Blob Storage存储资源的三个层次

  • storage account 存储实例的顶级层次
  • containers 相当于目录
  • blob 文件实体

Azure 门户点击Storage Account,查看当前tenant的Storage Account,进入其中某个account, 关于存储,提供了一个Explorer工具
左侧工具 Blob Services - Containers 创建容器csd-commom,(意外地发现之前做App Services备份地Deploy Packages在这里)
进入容器,可以直接使用页面提供的上传入口,上传可以填写一个folder,文件上传时自动使用该folder作为子目录
官方教程:使用 Azure 存储在云中上传图像数据
$blobStorageAccount(存储账户)
$blobStorageAccount(容器)
myAppServicePlan(应用服务计划)
myResourceGroup(资源组)
$webapp(App Service)dotNet Blob Uploader

YourStorageAccount — Settings — Access keys 查看账户密钥以及连接字符串

dotNet Blob Client Package: Azure.Storage

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
using Azure.Storage;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

...
public Task<bool> UploadFileToStorage(Stream fileStream){
// Create a URI to the blob
Uri blobUri = new Uri("https://" +
_storageConfig.AccountName +
".blob.core.windows.net/" +
_storageConfig.ImageContainer +
"/" + fileName);
// connection credential
StorageSharedKeyCredential storageCredentials =
new StorageSharedKeyCredential(_storageConfig.AccountName, _storageConfig.AccountKey);

// Create the blob client.
BlobClient blobClient = new BlobClient(blobUri, storageCredentials);

// Upload the file
await blobClient.UploadAsync(fileStream);

return await Task.FromResult(true);
}

public static async Task<List<string>> GetThumbNailUrls(AzureStorageConfig _storageConfig)
{
List<string> thumbnailUrls = new List<string>();

// Create BlobServiceClient from the account URI
BlobContainerClient container = new BlobContainerClient(connectionString, _storageConfig.ThumbnailContainer);

// Get reference to the container
BlobContainerClient container = blobServiceClient.GetBlobContainerClient(_storageConfig.ThumbnailContainer);

if (container.Exists())
{
foreach (BlobItem blobItem in container.GetBlobs())
{
thumbnailUrls.Add(container.Uri + "/" + blobItem.Name);
}
}

return await Task.FromResult(thumbnailUrls);
}

Blob SDK for Node.js: @azure/storage-blob
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 express = require('express')
const bodyParser = require('body-parser')
const multer = require('multer')
const _fs = require('fs')
const { StorageSharedKeyCredential,
BlobServiceClient } = require('@azure/storage-blob')
const {AbortController} = require('@azure/abort-controller')
const app = express();

// init blob client
const STORAGE_ACCOUNT_NAME = 'YourResourceGroupName'
const CONTAINER_NAME = 'BlobStorageContainerName'
const ACCOUNT_ACCESS_KEY ='****************************'
const ONE_MEGABYTE = 1024 * 1024;
const FOUR_MEGABYTES = 4 * ONE_MEGABYTE;
const ONE_MINUTE = 60 * 1000;
const aborter = AbortController.timeout(30 * ONE_MINUTE);
const credentials = new StorageSharedKeyCredential(STORAGE_ACCOUNT_NAME, ACCOUNT_ACCESS_KEY);

const blobServiceClient = new BlobServiceClient(`https://${STORAGE_ACCOUNT_NAME}.blob.core.windows.net`,credentials);

const containerClient = blobServiceClient.getContainerClient(CONTAINER_NAME);
//app.use(bodyParser.urlencoded({ extended: false }));
const upload = multer({dest:'/uploads'})
app.post('/upload',upload.single('file'), async (req,res)=>{
var des_file = __dirname + '/tmp/' +req.file.originalname;
const stream = _fs.createReadStream(req.file.path)
const blobClient = containerClient.getBlobClient(req.file.originalname);
const blockBlobClient = blobClient.getBlockBlobClient();
const uploadOptions = {
bufferSize: FOUR_MEGABYTES,
maxBuffers: 5,
};
const result = await blockBlobClient.uploadStream(
stream,
uploadOptions.bufferSize,
uploadOptions.maxBuffers,
aborter);
res.json(result)
})
const port =process.env.PORT||3000;
app.listen(port,()=>{
console.log('server on port:', port)
})

Azure Blob js SDK

—>自己的栗子

file input

前端文件上传入口一律使用input type=”file”, 关于UI的优化可参考Angular-Tips

图片以base64存放关系数据库

File对象转Uri

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<input type="file" (change)="handleUpload($event)">
————————————————————————————
handleUpload(event) {
const fileInput = event.target;
const imgFile: File = fileInput.files[0];
if (imgFile.type !== 'image/jpeg' && imgFile.type !== 'image/png') {
this.msgService.error('You can only upload JPG file or PNG file.');
return;
}
if (imgFile.size! / 1024 > this.logoSizeLimit) {
this.msgService.error(`Image must smaller than ${this.logoSizeLimit}k.`);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
this.logoUri = e.target.result;
}
reader.readAsDataURL(imgFile);
}

logoUri即图片经Bese64编码的字符串,可以直接存入数据库字段,可放入img:src作为上传预览

文件和流

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

File继承Blob, File作为特殊的Blob,可以用在任意的 Blob 类型的 context 中。比如FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send()

上一节使用的FileReader.readAsDataURL方法,读取指定的 Blob 或 File 对象。读取操作完成的时候,readyState 会变成已完成DONE,并触发 loadend 事件,同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。

URL.createObjectURL(object)返回一个DOMString,其包含object的URL,console中输出的话所谓的DOMString形如

1
blob:https://localhost:44362/fd57b5f3-a3b9-47ae-bd9d-56fc9012fb83

其生命周期与当前document相同,调用URL.revokeObjectURL释放

canvas.toBlob

1
2
3
4
5
6
7
8
9
10
function canvas2file(){
var image = document.querySelector('img');
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height= image.height;
var ctx = canvas.getContext("2d");
ctx.drawImage( image, 0, 0 );
console.log(canvas.toBlob());//转换成bold类型
console.log(canvas.toDataURL());//转换成dataURL类型
}

格物致知:从响应式编程理解‘流’
nodejs_stream

props

略 见React 子组件传参

context

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Tools  from "./Tools"
const AppContext = React.createContext(null);
const [ var1, var2, var3] =[ "111", "121", "311" ]
const App = () => {
return (
<AppContext.Provider value={
{
var1,
var2,
var3
}
}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<Tools />
</AppContext.Provider>
);
};
export { App, AppContext };

Tools.tsx略
ToolButton.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { AppContext } from "./App";
export default () => {
return (
<AppContext.Consumer>
{({ var1, var2, var3 }) => (
<>
<button >{var1}</button>
<button >{var2}</button>
<button >{var3}</button>
</>
)}
</AppContext.Consumer>
);
};

hook

这里不只是组件交互的范畴,React hook是一套新的状态管理API
useState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useState } from 'react';

function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

useState使用闭包定义setXX函数(参考函数式编程看React Hooks),大致如下
1
2
3
4
5
6
7
8
9
let _state;
function useState(initialState) {
_state = _state || initialState; // 如果存在旧值则返回, 使得多次渲染后的依然能保持状态。
function setState(newState) {
_state = newState;
render(); // 重新渲染,将会重新执行 Counter
}
return [_state, setState];
}

useEffect
Effect是指获取数据,订阅,Dom操作等。useEffect跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API,即组件在初始化完成、重新渲染、即将销毁时执行useEffect指定的逻辑。
1
2
3
4
5
6
7
8
9
10
/**
* Accepts a function that contains imperative, possibly effectful code.
*
* @param effect Imperative function that can return a cleanup function
* @param deps If present, effect will only activate if the values in the list change.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useeffect
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;

useEffect接受一个‘事件’列表作为可选参数
关于订阅/取消订阅和清除函数
常见于需要在组件初始化后(componentDidMount)设置订阅,在组件销毁前(componentWillUnmount)取消订阅以免内存泄漏,对应于使用Effect钩子的函数式React组件:
1
2
3
4
5
6
7
8
9
10
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

Effectf返回一个函数,该函数会作为Effect钩子的清除函数,React组件自动在销毁前执行清除函数
useContext
上面的ToolBtn经改写变为
1
2
3
4
5
6
7
8
9
10
export default () => {
const {var1, var2, var3} = useContext(AppContext);
return (
<>
<button >{var1}</button>
<button >{var2}</button>
<button >{var3}</button>
</>
)
};

useCallback
其他hooks

关于函数式组件和Hooks

使用Hooks代替class中的生命周期函数,是函数式组件进行逻辑复用、状态管理的方式

旧的思维:“我在这个生命周期要检查props.A和state.B(props和state),如果改变的话就触发xxx副作用”。这种思维在后续修改逻辑的时候很容易漏掉检查项,造成bug。新的思维:“我的组件有xxx这个副作用,这个副作用依赖的数据是props.A和state.B”。从过去的命令式转变成了声明式编程。
———— csr632 《为什么 React 现在要推行函数式组件,用 class 不好吗?》下的回答

用户删除后挂起30日,期间可以还原

install packages:

1
yarn

execuate project
1
yarn [YourScriptInPackageJSON]

yarn 和 npm

  • yarn 速度更快(并行和离线缓存)
  • lock 版本 (与package-lock.json)

yarn.lock

  • 如package-lock.json 开发过程中不应删除重建 应当及时提交
  • yarn 过程根据yarn.lock的依赖树安装及拷贝package 且对与package.json不一致的依赖进行更新
  • 使用 yarn upgrade 根据package.json对依赖进行升级 并更新yarn.lock

yarn 和 yarn install

yarn install is used to install all dependencies for a project. This is most commonly used when you have just checked out code for a project, or when another developer on the project has added a new dependency that you need to pick up.
If you are used to using npm you might be expecting to use —save or —save-dev. These have been replaced by yarn add and yarn add —dev. For more information, see the yarn add documentation.

Running yarn with no command will run yarn install, passing through any provided flags.

查看包版本 yarn info xxpkg
yarn list xxpkg —depth=0

从私有Repository安装

配置.yarnrc.yml 即yarn resouce configure

1
2
3
4
5
6
7
8
npmRegistries:
//qqstone.jfrog.io/artifactory/api/npm/Viewer/:
npmAlwaysAuth: true
npmAuthIdent: c2hpLnFpdUAlbnZpc3RhY28uY236QUtDcDhuSER6YWo3NDNIekNDOVRxOW1Kb0tGVHVaKU5yZ2N4aU5jaWVRQ0hEb2tNR0ROTE43TGkybV5aRkVzSkxkUzdMYkDudA==

npmScopes:
qqsjfrog:
npmRegistryServer: "https://qqstone.jfrog.io/artifactory/api/npm/Viewer/"

TroubleShooting

cannot be loaded because running scripts is disabled on this system.