Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

关于dashboard需要导出成PDF这件事

Posted on 2022-07-25 | In BusinessSolutions

这篇算是业务小笔记吧。

需求描述: 有个 dashboard 界面,需要新增一个 export PDF 的功能。这个功能需要在两个地方存在:1. monitor dashboard 可以直接导出 2. list 页面可以快速导出。

需求分析: 又是一个前后端都可以做的 feature.但是如果从前端可以拿出什么方案呢?我用到的方案就是 dom node 转 canvas 之后再转 PDF。

这个方案用到了两个 package. 1. html2canvs 2. jsPDF

方案很简单。拿到 dom node 后使用 html2canvas 转 image, 再把 image 转 jsPDF。

来看一个简单的 demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import jsPDF from "jspdf";
import html2canvas from "html2canvas";

const HTMLToCanvas = async (selector) => {
const canvas = await html2canvas(document.querySelector(selector));
return canvas;
};

export const domNodeToPDF = async (selector, pdfName) => {
const canvas = await HTMLToCanvas(selector);
const pageData = canvas.toDataURL("image/jpeg", 1.0);
const pdf = new jsPDF({
orientation: "l",
unit: "pt",
format: [canvas.width, canvas.height],
});
pdf.addImage(pageData, "JPEG", 0, 0, canvas.width, canvas.height);
pdf.save(pdfName + ".pdf");
};

这个方案的可行性已经验证没问题。那么下一步,看一下这个方案的局限性在哪?

其实我能想到会遇到的问题就是当 dashboard 的数据巨大的时候,在渲染耗时上会不会存在问题?

比如一年量的数据。关于这个问题我能想到的是,改变交互,使用 web worker 进行 canvas 的转换和渲染。

这部分的工作都交给 web worker 来执行。执行完成后以 notification 的方式通知用户下载。

1.Pure React.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

pure react

##react 与 react-dom的区别

原本的 react package 被拆分为 react 及 react-dom 两个 package。其中 react package 中包含 React.createElement、 .createClass、 .Component, .PropTypes, .Children 这些 API,而 react-dom package 中包含 ReactDOM.render、 .unmountComponentAtNode、 .findDOMNode。

根节点结构

使用reactDom挂载节点,React作为jsx语法载体. 使用react.createElement作为原子单位进行开发。

1
2
3
4
5
6
7
8
9
const App = () => {
return React.createElement(
'div',
{id: 'yeah'},
React.createElement('h1', {}, 'adopt-me')
);
}

ReactDOM.render(React.createElement(App), document.getElementById('root'));

createElement函数参数解读

  1. tagName
  2. attribute
  3. children, 可以是数组, 也可以是另一个虚拟dom

可复用元素

组件重用的基础在于, createElement函数, 该函数的第三个参数让我们可以不断地重用组件, 只要这个组件符合react.createElement的规范,都可以直接传入React.createElement,作为一个虚拟dom节点.挂载在根节点上,导致了我们现在的开发过程, 其实就是不断的开发新的组件。

传递组件的props

// 这里可以使用ES6的解构赋值特性

1
2
3
4
5
6
7
8
9
10
11
const pet = ({ name, name1}) => {
return React.createElement(
'div',
{},
[
React.createElement('h2', {}, name),
React.createElement('h2', {}, name1),
]

)
}

2.tools.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

tools链

  1. npm

    • npm是什么
    • npm工程初始化
    • npm遇到什么问题
    • 语义化的版本控制
  2. prettier

    • 格式美化和统一

    • 对于团队成员都应该使用相同的格式

    • 配置保存后强制修复格式

      配置prettier步骤

    1. npm install prettier -D

    2. 在vscode安装插件

    3. 配置vscode

      • setting中配置以下两项:
        format on save: 打开
        require config - require config 对勾

      • 创建一个新的config文件,

        文件名: .prettierrc

        内容: “singleQuete”: true // 开启字符串单引号检测

        1
        2
        3
        {
        "singleQuete": true
        }
  3. ESlint set up

    1
    npm install -D eslint eslint-config-prettier
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "extends": ["eslint:recommended", "prettier", "prettier/react"],
    "plugins": [],
    "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module",
    "ecmaFeature": {
    "jsx": true
    }
    },
    "env": {
    "es6": true,
    "browser": true,
    "node": true
    }
    }
  4. git

    1
    2
    3
    4
    5
    6
    7
    node_modules/
    .DS_Store
    .cache/
    dist/
    coverage/
    .vscode/
    .env
  5. parcel

    1
    npm install -D parcel-bundler

    增加script命令

    1
    "dev": "parcel src/index.html",
  6. 安装react and react-dom
    引入的时候需要我们使用, 按需引入, 因为构建器会进行摇树算法, 如果整个引入,那么摇树算法的作用就很小

  7. 模块化: 一个组件一个文件, 不能多组件一个文件

模块化使用es的import/export, 在react中,我们只需要返回特定的render函数, 这个render函数就是组件。

4.Hook.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

Hook

Hook赋予了我们, 不使用classComponent也可以使用组件上的特性。例如: 生命周期,state状态.

  1. useState: 返回一个数组, 数组包含两个元素, 1) 对应的默认值, 2) 更新值的函数
    1
    2
    3
    4
    5
    6
    // 引入
    import React, {useState} from 'react'

    const [location, setLocation] = useState('default Value')
    // 此时, location 为被跟踪的变量
    // setLocation 为设置Location的钩子函数

这个原则对所有的钩子函数使用
注意, 不要把钩子的引入放到if-else, for等循环语句中.
钩子生效最重要的是根据钩子初始化的顺序.例如vue中的生命周期, 有严格的执行顺序.使用了分支语句, 那么就可能导致问题的产生.

  1. 自定义组件钩子
    我们写的组件,之前是基于props的数据流去处理, 如果使用hook那么一切都变成了函数.也就代替了之前使用,Class组件构建页面的结构.

自定义钩子的步骤:

  • 引入useState系统钩子
    1
    import {useState} from 'react'
  • 定制入参
    注意了, 入参就是你初始化钩子的参数.比如例子里, 一个下拉框, 入参为:

labalName: label标签的内容

defaultValue: 下拉框的默认值

options: 下拉框的数据list

  • 规范出参
    对于出参,我们遵循系统提供的hook的范式, 第一个参数为变量,最后一个参数为该变量的setState

  • 在出参中,我们有该变量的setState, 那么我们怎么实现这个功能呢?答案是, 使用useState.
    useState, 其实就是, 传入一个变量,返回两个参数, 第一个参数是变量的值, 这个值可被跟踪, 也就是当我们调用第二个参数的函数时(也就是setState), 那么就会触发这个值的更新.从而实现了数据侦听.

  1. Effect钩子 对比生命周期的概念
    effect钩子与渲染过程是分开的.在第一次渲染的时候,effect是不会被触发的.

作用一: 调用服务

1
2
3
useEffect(() => {
pet.breeds('dog').then(console.log, console.error)
})

作用二: 更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
useEffect(() => {
// 一个useEffect体里面, 第二个入参非常重要,
// 第二个入参是一个数组, 这个数组的内容是, 在参数1中的匿名函数存在的依赖项
// 在该例子中,我们不传入animal那么, 当animal更新时, 不会触发这个钩子
// 也就是只在渲染后执行一次匿名函数, 之后修改不会再次更新

// 注意, 如果第二个参数不传的话, 会导致无限循环, 至于为什么?
// 在effect钩子中, 我们肯定回去改变某些数据, 而不传我们要侦听的依赖项
// 那么effect的行为就是默认监听所有变化, 这就导致了, 更新的时候触发effect, 但是呢在effect里又会触发更新
console.log('触发effect生命周期', animal)
// 明白了, 这段代码的意思是, 每次触发更新的时候, 先把值清空
setBreeds([])
setBreed('')
pet.breeds(animal).then(({breeds}) => {
const breedStrings = breeds.map(({name}) => name)
setBreeds(breedStrings)
}, console.error)
}, [animal, setBreed, setBreeds])

解读一下, effect本质上解决我们在首次渲染后, 需要做的事, 比如: 获取数据, 记住执行顺序就好, effect相当于一个promise, 在完成渲染后执行, 如果存在依赖, 那么还会再次触发.就那么简单, 没啥.

5.asyncAndRouter.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

异步和路由

  1. 异步函数 async
    在react使用异步,与正常我们写异步函数没什么大的区别.主要时在请求完成后, 我们使用hook把数据更新, 仅此而已。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 异步API
    const [pets, setPets] = useState([])


    async function requestPets() {
    const { animals } = await pet.animals({
    location,
    breed,
    type: animal
    })

    setPets(animals || [])
    }
  2. 路由基础概念.

  • 渲染锚点
  • 触发路由渲染的入口
  • 动态路由参数
    1
    2
    3
    const test = <Details path="/details/:id" />

    // 在jsx中我们通过props去访问这个路由参数

Array Methods.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

Array Methods

find/findIndex/includes

  1. array.find是一个高阶函数, 会返回匹配回调函数内符合的第一个元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr = [{a: 1}, {a: 2}, {a: 3}];

    arr.find(function match(v){
    return v && v.a > 1;
    });
    // {a: 2}
    // 条件不符合的时候返回undefined

    arr.findIndex(function match(v) {
    return v && v.a > 10;
    }); // -1
  2. includes 运算符
    1
    2
    3
    4
    5
    var arr = [10, 20, NaN];

    arr.includes(10);

    arr.includes(NaN);

flat & flatMap

  1. Array.flat: flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

depth 可选: 指定要提取嵌套数组的结构深度,默认值为 1。

使用flat函数会移除所有的空项 [1, 2, 3, , 5] => [1, 2, 3, 5]

1
2
3
[1, 2, 3, [4, 5, [6, 7]]].flat(); // 默认展开的层级是1
[1, 2, 3, [4, 5, [6, 7]]].flat(2); // 这样就是完全展开
[1, 2, 3, [4, 5, [6, 7]]].flat(Infinity); // 这样就是完全展开
  1. Array.flatMap: flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

简而言之, 用cb处理数组, 再把数组扁平化

1
2
3
4
5
6
7
let arr = ["今天天气不错", "", "早上好"]

arr.map(s => s.split(""))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]

arr.flatMap(s => s.split(''));
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]

3.JSX.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

jsx

render函数转成jsx

JSX是render函数的语义化.两者作用相同.每一个createElment就是一个标签.嵌套关系保留.createElment的参数统一放入jsx的标签里.

为react配置eslint

1
npm install -D babel-eslint eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
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
{
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"prettier",
"prettier/react"
],
"rules": {
"react/prop-types": 0,
"no-console": 1
},
"plugins": ["react", "import", "jsx-a11y"],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeature": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true,
"node": true
},
"settings": {
"react": {
"version": "detect"
}
}
}

jsx composite components & expressions

在我们的jsx, return jsx语法中,我们可以在返回之前执行更多的逻辑, 得到最后的render函数.

Async Await.md

Posted on 2020-04-12 | In JavaScript_The Recent Parts

#Async Await ES2017 (这一块除了基础使用部分, 还要重新看一次)

Async Function

在ES6里, 我们有了Promise,这让我们的callback hell的处境又所好转, 但是, Promise依旧还是一个回调地狱.

1
2
3
4
5
6
7
8
9
10
fetchCurrentUser().then(function onUser (user) {
return Promise.all([
fetchArchivedOrders( user.id ),
fetchCurrentOrders( user.id )
]);
}).then(function onOrder([
archivedOrders, currentOrders
]){
// do something
})

所以我们有了async/await.一种同步语法写异步的操作.

在我们了解async/await的时候,让我们从generators开始, 在各种node写的server端的库都有类似的一个runner函数, 你用generators产生了一个promise, 然后等待这个promise完成后, 给你返回值, 然后再往下走。感受一下, 这个是不是很像我们的async/await语法。

1
2
3
4
5
6
7
8
9
10
runner(function *main() {
var user = yield fetchCurrentUser();

var [ archivedOrders, currentOrders ] = yield Promise.all([
fetchArchivedOrders( user.id ),
fetchCurrentOrders( user.id )
])

// ....
})

接下来看一下async/await

1
2
3
4
5
6
7
8
9
10
11
12
async function main() {
var user = await fetchCurrentUser();

var [ archivedOrders, currentOrders ] = await Promise.all([
fetchArchivedOrders( user.id ),
fetchCurrentOrders( user.id )
])

// ...
}

main();

Async Iteration

我们平常使用的map, forEach函数都是基于同步的循环。他们遇到promise不知道怎么处理。

当我们在循环里嵌套await其实是存在问题的。基于我们的练习, 你可能会写出这样的代码

1
2
3
4
5
6
7
8
async fuction fetchFiles (files) {
var prs = files.map(getFile);

// 发现问题了吗?回调函数并不是一个async函数
Prs.forEach( function each(pr) {
console.log(await pr);
})
}

Async Function Problems —(不是很懂, 要回去再看一次)

Async Generators with yield

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function fetchURLs() {
var result = [];

for(let url of urls) {
let resp = await fetch( url );
if(resp.status == 200) {
let text = await resp.text();
result.push( text.toUpperCase() );
}else{
result.push(undefined);
}
}

return result;
}

6.classComponents.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

Class Component

基于ES6的Class组件

  1. 它基于React.components

  2. 我们要使用super来传递上一级给的Props

  3. 使用constructor来初始化this.state,以及事件绑定

  4. 使用生命周期

错误处理

使用componentDidCatch生命周期钩子.这个钩子的作用是捕获子组件(记得啊,是子组件)的错误, 像try-catch函数.

getDerivedStateFromError 如果我们产生了错误, 那么就会触发这个钩子函数.那么在这个钩子函数中, 我们就可以进行处理了。

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
// mostly code from reactjs.org/docs/error-boundaries.html
import React, { Component } from "react";
import { Link, Redirect } from "@reach/router";

class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, redirect: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error("ErrorBoundary caught an error", error, info);
}
componentDidUpdate() {
if (this.state.hasError) {
// 5s后自动进行重定向
setTimeout(() => this.setState({ redirect: true }), 5000);
}
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />;
}

if (this.state.hasError) {
// 如果捕捉到错误, 则显示
return (
<h1>
There was an error with this listing. <Link to="/">Click here</Link>{" "}
to back to the home page or wait five seconds
</h1>
);
}
// 如果没有捕捉到错误, 那么你应该把组件往下传递
return this.props.children;
}
}

export default ErrorBoundary;

7.content.md

Posted on 2020-04-12 | In Complete Intro to ReactV5

content 上下文

类似state但是它基于整个应用.

类似一个evenBus

并且, 尽量避免使用.

content和evensBus类似, 所以这两个不要混用.

挖坑吧.这个content暂时不影响我使用react.

说说概念.你现在要把一些东西存起来, 首先第一步, 初始化。由于这是全局的状态, 那么你需要有一个注入的过程, 因为不是每个组件都需要这个content。OK, 假设你现在注入了上下文。那么你就有个取出数据的过程。所以, 总结一下, 你需要掌握的有:

  1. content初始化
    1
    2
    3
    4
    5
    import { createContext } from "react";

    const ThemeContext = createContext(["green", () => {}]);

    export default ThemeContext;
  2. content注入上下文
    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
    import React, { useState } from "react";
    import ReactDOM from "react-dom";
    import SearchParams from "./SearchParams";
    import { Router, Link } from "@reach/router";
    import Details from "./Details"
    // 先引入
    import ThemeContext from './ThemeContext'

    const App = () => {
    // 实例化钩子
    const themeHook = useState('darkblue')
    return (
    // 使用注入标签
    <ThemeContext.Provider value={themeHook}>
    <div>
    <header>
    <Link to="/">Adopt Me!</Link>
    </header>
    <Router>
    <SearchParams path="/" />
    <Details path="/details/:id" />
    </Router>
    </div>
    </ThemeContext.Provider>
    );
    };

    ReactDOM.render(<App />, document.getElementById("root"));
    注意, 在使用content后, 所有的子组件都可以访问到
  3. 在子组件取出content
    1
    2
    3
    4
    5
    6
    7
    8
    // 引入 
    import ThemeContext from './ThemeContext'

    // 实例化 在组件体中
    // context 上下文
    const [theme] = useContext(ThemeContext)
    // 使用
    // 直接访问 theme就好
  4. 更新content
    你看你都用上了useContext你还不知道怎么更新值吗?小可爱

以上是在hook里面使用context的方式, 接下来让我们看一下class组件内, 我们该如何使用.

因为在class里我们木有hook, 但是引入方式是一样的, 不一样的地方在使用

1
2
3
4
5
6
<ThemeContext.Consumer>
{(themeHook) => (
<button style={{'color': themeHook[0]}}>Adopt {name}</button>
)}

</ThemeContext.Consumer>

解释一下, 在ThemeContext里, 我们放一个匿名函数, 在这个函数体内我们可以访问到的参数就一个, 其实就是一个hook,第一个为值本身, 第二个为修改他的函数.

但是, 注意了, 这里有个问题, 我们能保住这个context的基准是, 我们要使用link也就是react的官方路由来跳转,防止页面unmounted.

12…7>

Hawei

Fullstack Engineer

61 posts
16 categories
3 tags
RSS
© 2022 Hawei
Powered by Hexo
|
Theme — NexT.Muse v5.1.4