日常学习

roadhog(路霸)

  • 是什么?

是一个脚手架工具,提供了各种丰富的功能。

  • 为什么用?

由于 create-react-app 的默认配置不能满足需求,而他又不提供定制的功能,于是基于他实现了一个可配置版。

  • 为什么叫 roadhog ?

roadhog 即路霸,和 dva 一样,是守望先锋中的另一名英雄,希望能为 dva 保驾护航。

dva

  • 是什么?

dva 首先是一个基于 reduxredux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-routerfetch,所以也可以理解为一个轻量级的应用框架。

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。

  • 为什么用?

概念太多,并且 reducer, saga, action 都是分离的(分文件)。

编辑成本高,需要在 reducer, saga, action 之间来回切换。

saga 书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程。

tree命令

下载 tree 命令的 二进制包,安装 tree 命令工具

  • 打开进入 Tree for Windows 页面,选择下载 Binaries zip 文件。
  • 解压压缩包,找到压缩包内的 bin 目录
  • bin目录配置到环境变量中即可

测试

tree :列出所有的文件夹

tree <path> /f:列出指定目录下的所有文件

yarn

# 国内源
$ npm i yarn tyarn -g
# 后面文档里的 yarn 换成 tyarn
$ tyarn -v

添加环境变量

# windows系统:
# 获取 global bin 的路径
$ yarn global bin
C:\Users\Administrator\AppData\Local\Yarn\bin
# 复制上面的 global bin 的路径,添加到系统环境变量 PATH。

本地验证

$ yarn global add serve
$ serve ./dist

Serving!

- Local:            http://localhost:5000
- On Your Network:  http://{Your IP}:5000

Copied local address to clipboard!

react修改对象数据

数据

[
    {
        name: `shopping`,
        complete: false
    },
    {
        name: `running`,
        complete: false
    }
]

如果要修改数组中的第一项的complete的值, 在vuethis.$set(this.arr[0],complete, true)即可, 修改之后数据会自动更新驱动视图,而在react中是这样的

数组

updateItem = item => {
    // 首先拿到 item 的索引, 也可以从参数中传递过来
    let index = this.state.arr.indexOf(item)
    // 然后根据索引修改
    this.state.arr[index][`complete`] = true
    // 这个时候并不会更新视图, 需要 setState更新 arr 
    // (注意 setState 是一步操作, 不要轻易去根据 setstate 的状态去做接下来的事情), 非要这样的话可以给 setState 传递一个函数`this.setState(() => {})`进行操作
    this.setState({
        arr: this.state.arr
    })
}

对象

updateItem = newValue => {
    // 基本同理
    this.state.obj.key = newValue
    this.setState({obj: this.state.obj})
}

父组件调用子组件

  • 通过ref拿到子组件引用

    <PostMenu
      ref={instance => { this.menu = instance; }}
      initRentMoneyIndex={7}
      onMoneyChange={this.onMoneyChangeCallback.bind(this)}
      onGenderChage={this.onGenderChageCallback.bind(this)}>
    </PostMenu>
    
  • 然后通过引用就可以直接调用子组件方法

    // 调用子组件方法
    this.menu.onConfirmClickCallback()
    

compose函数组合

// 函数组合
const compose = (...funcs) => {
  return function(x) {
    return funcs.reduceRight((acc, func) => {
      return func(acc)
	  }, x)
    }
}
// 单一功能,转大写
function toUpper(x) {
	return x.toUpperCase()
}
// 单一功能,转数组
function toArray(x) {
	return x.split('')
}

const toUpperArray = compose(toArray, toUpper)
toUpperArray('abc') // ['A', 'B', 'C']

小程序转发

  • 列表页转发其中的某一个item,如何通过转发的卡片进入帖子详情?
  • 进入详情,需要帖子的ID,如何拿到这个ID
  • 到达详情页如何显示一个按钮,用户可以返回首页?

实现

    1. 通过按钮转发,可以携带一个data-shareInfo参数
<Button
    plain="true" 
    open-type="share" 
    className="share" 
    data-shareInfo={this.state.comment.id}>
    分享给好友
</Button>
    1. 通过转发回调拿到这个参数(page页面)
  // 转发回调
  onShareAppMessage(options) {
    let id = options.target.dataset.shareinfo
    let shareObj = {
      title: '',
      path: '', 
      imgUrl: '',
      success(res) {
      },
      complete() {
        console.log('complete')
      }
    };
    // 来自页面内的按钮的转发
    if (options.from == 'button') {
      // 路由到详情页,添加一个id、action参数
      shareObj.path = `/pages/xxx/xxx?Id=${id}&action=9527`
    }
    return shareObj;
  }
    1. 当页面渲染后根据转发时传递的action判断用户是否通过卡片进入页面
  componentDidMount() {
    // 判断是否通过转发进入该页面
    let action = this.$router.params.action
    if (action && action === '9527') {
      // 如果是的话修改state, 显示一个返回首页的按钮   
      this.setState({ shareAction: true })
    }
  }

  // 返回首页的函数	
  goBackIndex() {
    Taro.redirectTo({
      url: '/pages/index/index'
    })
    this.setState({ shareAction: false })
  }

render() {
    return (
    	{
            this.state.shareAction && 
         	<View onClick={this.goBackIndex} className="goIndex">
             	首页
         	</View>
        }
    )
}

Generator

  • node-thunkify

js语言是传值调用。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

怎样去写一个Thunkify模块呢?

function thunkify(fn) {
	if(typeof fn !== 'function') {
		throw new Error('params must is a function!')
	}
	return function() {
		var ctx = this
		var args = Array.prototype.slice.call(arguments)
		return function(callback) {
            // 通过变量,保证回调只执行一次
			var called;
			args.push(function() {
				if(called) return
				called = true
				callback.apply(null, arguments)
			})

			try {
				fn.apply(ctx, args)
			} catch(err) {
				callback(err)
			}
		}
	}
}

测试:

function hello(a, b, cb){
	var sum = a+b;
	cb(sum)
	cb(sum)
}

thunkify(hello)(1, 2)(console.log)
=> 3

经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数。

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。

那么Thunk函数有什么用呢?

Thunk 函数可以用于 Generator 函数的自动流程管理。

var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFile('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFile('/etc/shells');
  console.log(r2.toString());
};

怎样去调用执行这个gen函数呢?

var g = gen()

// 调用next函数,返回一个对象,对象的value接收一个回调函数
// 在回调函数中我们可以将函数的执行权移交给Generator函数
g.next().value(function(err, data) {
    if(err) throw err
    g.next(data).value(function(err, data) {
        if(err) throw err
        g.next(data)
    })
})
// 可以看到在上述代码中将用一个回调函数传入next方法的value属性中。
// 可以使用递归来完成这个过程。

怎么做呢?

// 需要传入一个Generator函数
function run(gen) {
    let g = gen()
    
    // 回调函数
    function next(err, data) {
        let result = gen.next(data)
        if(result.done) return
        result.value(next)        
    }
    next()
}

// 调用
run(gen)

快速求交集

var aArr = ['a', 'b', 'c', 'd'];
var bArr = ['c', 'd', 'e', 'f'];

// 常规的方式  12436条数据耗时 112 毫秒
function getIntersection(a, b) {
    return a.filter(v => b.includes(v));
}

// 快速方式  12436条数据耗时 5 毫秒
function getIntersection(a, b) {
    const bSet = new Set(b);
    return a.filter(v => bSet.has(v));
}
# 同理可以求并集、差集

本地存储

不要给sessionStoragelocalStorage设置为nullundefined

因为当你读取的时候你会发现,它会变成"null"或"undefined"

文件下载

const blob = new Blob([res], { type: 'application/vnd.ms-excel' });
const blobUrl = window.URL.createObjectURL(blob);
const a = document.getElementById('download-link');
const filename = 'onecloud_nodes.csv';
a.href = blobUrl;
a.download = filename;
a.click();
window.URL.revokeObjectURL(blobUrl);

本质:

<a href="http://somehost/somefile.zip" download="filename.zip">Download file</a>
const blob = new Blob([`\uFEFF${response}`], { type: 'text/csv,charset=UTF-8' });
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
const filename = `download-${new Date().getTime()}.csv`;
a.href = blobUrl;
a.download = filename;
a.click();
a.remove();
 window.URL.revokeObjectURL(blobUrl);

utf-8保存的csv格式要让Excel正常打开的话,必须加入在文件最前面加入BOM(Byte order mark).

BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。

/**
 * 前端构造数据并下载为CSV
 * 
 * @param {*} header ['ID','name']
 * @param {*} content [['1','lemon'],['2','lara']]
 */
const frontEndDownloadCsv = (header, content) => {
  if (!header || !content) {
    return;
  }
  let context = "";
  context = `${header.join(',')}\n`;
  for (let i = 0; i < content.length; i += 1) {
    const item = content[i];
    context += `${item.join(',')}\n`;
  }
  downLoadHelper(context);
}

const downLoadHelper = (response) => {
  const blob = new Blob([`\uFEFF${response}`], { type: 'text/csv;charset=UTF-8' });
  const blobUrl = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  const filename = `download-${new Date().getTime()}.csv`;
  a.href = blobUrl;
  a.download = filename;
  a.click();
  a.remove();
  window.URL.revokeObjectURL(blobUrl);
}

typeScript

  • declare定义的类型只会用于编译时的检查,编译结果中会被删除。

public private 和 protected

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivateprotected

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问(最严格)
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的(子类中可以访问父类的属性)

类和接口

声明一个接口变量,赋值一个符合性状的函数是OK的。

react生命周期(10)

  • 组件挂载四个方法

    constructor()

    static getDerivedStateFromProps() // 获取从props衍生出来的state

    render()

    componentDidMount() // 请求数据

  • 组件更新五个方法

    static getDerivedStateFromProps() // 适用于state的值,在任何时候都取决于props

    # 存在只有一个目的:让组件在 props 变化时更新 state。
    static getDerivedStateFromProps(props, state) {
        const { pathname, openKeys } = state;
        // 状态需要根据pathname改变而改变
        if (props.location.pathname !== pathname) {
          return {
            pathname: props.location.pathname,
            openKeys: [...new Set([...getDefaultCollapsedSubMenus(props), ...openKeys])],
          };
        }
        return null;
    }    
    

    shouldComponentUpdate() // 作用:性能优化

    render()

    getSnapshotBeforeUpdate(prevProps, prevState) // 获取快照、简介 返回值 DidUpdate方法接收

    // 这个方法可能出现在UI处理中,如需要以特殊的方式处理滚动位置。
    getSnapshotBeforeUpdate(prevProps, prevState) {
        // 添加了行的listItem
        if(prevProps.list.length < this.props.list.length) {
            return ...;
        }
        return null;
    }
    # 返回值可以被 componentDidUpdate 方法接收
    

    componentDidUpdate(prevProps, prevState, snapshot) // 首次渲染不会执行

    也可以在此处发起网络请求,别忘记添比较

    componentDidUpdate(prevProps) {
      // 典型用法(不要忘记比较 props):
      if (this.props.userID !== prevProps.userID) {
        this.fetchData(this.props.userID);
      }
    }
    
  • 组件卸载

    componentWillUnmount()

react-saga

import { createStore, applyMiddleware, compose } from 'redux'  //  引入createStore方法
import reducer from './reducer'   
import createSagaMiddleware from 'redux-saga' 
import mySagas from './sagas' 

const sagaMiddleware = createSagaMiddleware();

const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
const store = createStore( reducer, enhancer) 
sagaMiddleware.run(mySagas)

export default store  

react-redux

@connect(
  state => ({
    initVal: state.initVal,
    list: state.list
  }),
  dispatch => {
    return {
      inputChange: e => {
        dispatch({
          type: "change_input",
          value: e.target.value
        });
      },
      addItem: () => {
        dispatch({ type: "add_item" });
      }
    };
  }
)
class TodoList extends Component {
  componentDidMount() {}

  render() {
    const { initVal, list, inputChange, addItem } = this.props;
    return (
      <div>
        <Input value={initVal} onChange={inputChange} />
        <Button type="primary" onClick={addItem}>
          添加数据
        </Button>
        <ul>
          {list.map(item => (
            <li>{item}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

import store from "./store/index";

const App = (
  <Provider store={store}>
    <TodoList />
  </Provider>
);

nginx

启动nginx

  • (1) 直接双击nginx.exe,双击后一个黑色的弹窗一闪而过

  • (2) 打开cmd命令窗口,切换到nginx解压目录下,输入命令 nginx.exe 或者 start nginx ,回车即可

检查nginx是否启动成功

  • 直接在浏览器地址栏输入网址 http://localhost:80,回车

  • 在cmd命令窗口输入命令 tasklist /fi "imagename eq nginx.exe"

重启: nginx -s reload

关闭nginx

如果使用cmd命令窗口启动nginx,关闭cmd窗口是不能结束nginx进程的,可使用两种方法关闭nginx

(1)输入nginx命令 nginx -s stop(快速停止nginx) 或 nginx -s quit(完整有序的停止nginx)

(2)使用taskkill taskkill /f /t /im nginx.exe

获取cookie

// 获取Cookie
function getCookie(name) {
  return document.cookie.match(
    new RegExp('(^' + name + '| ' + name + ')=([^;]*)')
  ) == null
    ? ''
    : RegExp.$2
}

document.cookie
"uuid_tt_dd=10_2448552910-1563172509314-625231; dc_session_id=10_1563172509314.901950; Hm_ct_6bcd52f51e9b3dce32bec4a3997715ac=6525*1*10_2448552910-1563172509314-625231; __yadk_uid=5yzpFGuOimeXxgAwAh5ropulzy4qs5zX; __gads=Test; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1572256123,1572256592,1572512554,1572523258; dc_tos=q0fnem; c-login-auto=23; Hm_lpvt_6bcd52f51e9b3dce32bec4a3997715ac=1572850751"

/(uuid_tt_dd| uuid_tt_dd)=([^;]*)/.test(document.cookie) // true
/^hello| world/.test(' world') // true
/^hello| world/.test('world') // false

socket.io-client

// with ES6 import
import io from 'socket.io-client';

const socket = io('http://localhost');

socket.on('connect', function(){});
socket.on('event', function(data){});
socket.on('disconnect', function(){});

一个简单的websocket服务器:

const express = require('express');
const axios = require('axios');
const http = require('http');
const socketIO = require('socket.io');

const port = process.env.PORT || 4001;
const routes = require('./routes/index');

const app = express();
app.use(routes);

const server = http.createServer(app);

// 通过传入一个http server创建一个socketIO实例
const io = socketIO(server);

const getApiAndEmit = async socket => {
  try {
    const res = await axios.get(       "https://api.darksky.net/forecast/642c75e2261b3896a61a75e5d1e360eb/37.8267,-122.4233"
    );
    socket.emit('FromAPI', res.data.currently.temperature);
  } catch (error) {
    console.error(`Error: ${error.code}`);
  }
};

io.on('connection', socket => {
  console.log('New Client connected');
  getApiAndEmit(socket); // first exec
  setInterval(
    () => getApiAndEmit(socket), 1000 * 10
  );
  socket.on('disconnect', () => console.log('Client disconnected'));
});

server.listen(port, () => console.log(`Listening on port ${port}`));

客户端:

import socketIOClient from 'socket.io-client';

const socket = socketIOClient('http://127.0.0.1:4001');
socket.on('FromAPI', data => console.info({response: data}));

Helpful JS Snippets

all

const all = (arr, fn = Boolean) => arr.every(fn);
all([4,5,6], x => x > 1); // true

allEqual

const allEqual = arr => arr.every(val === arr[0]);

approximatelyEqual

const approximatelyEqual = (v1, v2, epsilon = 0.001) => Math.abs(v1 -v2) < epsilon;

arrayToCSV

converts the elements to strings with comma-separated values;

const arrayToCSV = (arr, delimiter = ',') => 
	arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');

attempt

const attempt = (fn, ...args) => {
    try {
        return fn(...args);
    } catch(e) {
        return e instanceof Error ? e : Error(e);
    }
}

**bifurcate **

const bifurcate = (arr, filter) => 
	arr.reduce((acc, val, i) => (acc[filter[i] ? 0 : 1].push(val), acc), [[],[]]);

bifurcate(['beep', 'boop', 'foo', 'bar'], [true, true, false, true]); 
// [ ['beep', 'boop', 'bar'], ['foo'] ]

bifurcateBy

const bifurcateBy = (arr, fn) => 
	arr.reduce((acc, val) => (acc[fn(val) ? 0 : 1].push(val), acc), [[],[]]);

byteSize

const byteSize = str => new Blob([size]).size;

capitalizeEveryWord

const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());

dayOfYear

const dayOfYear = date =>
  Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);

deepFlatten

const deepFlatten = arr => 
	[].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));

const deepFlatten = arr =>
	arr.reduce((a, v) => 
    	(Array.isArray(v) ? a.push(...deepFlatten(v)) : a.push(v), a), []);

flatten:指定深度

const flatten = (arr, depth=1) => 
	arr.reduce((a, v) => 
               a.concat(depth > 1 && Array.isArray(v) ? flatten(v, depth - 1):v), []);

shuffle:洗牌数组

const shuffle = ([...arr]) => {
    let m = arr.length;
    while(m) {
        const i = Math.floor(Math.random() * m--);
        [arr[m], arr[i]] = [arr[i], arr[m]];
    }
    return arr;
}

根据parent_id生成树结构

const comments = [
  { id: 1, parent_id: null },
  { id: 2, parent_id: 1 },
  { id: 3, parent_id: 1 },
  { id: 4, parent_id: 2 },
  { id: 5, parent_id: 4 }
];

const nest = (items, id = null, link = 'parent_id') =>
  items.filter(item => item[link] === id)
       .map(item => ({...item, children: nest(items, item.id)}));

正则,删除html标记

const stripHTMLTags = str => str.replace(/<[^>]*>/g, '');

stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>'); 
// 'lorem ipsum'

is:检查值👶

const is = (type, val) => 
	![,null].includes(val) && val.constructor ===type;

is(Number, 1); // true

// ### 可以通过这个方法排除 undefined 和 null
[,null].include() // true
[,null].include(null) // true
[,null].include(undefined) // true

全等判断

const equals = (a, b) => {
  if (a === b) return true;
  if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
  if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
  if (a.prototype !== b.prototype) return false;
  let keys = Object.keys(a);
  if (keys.length !== Object.keys(b).length) return false;
  return keys.every(k => equals(a[k], b[k]));
};

平滑滚动到顶部

const scrollToTop = () => {
    const c = document.documentElement.scrollTop || document.body.scrollTop;
    if(c > 0) {
        window.requestAnimationFrame(scrollToTop);
        window.scrollTo(0, c - c / 8);
    }
}

Grid 自适应布局

<div class="container">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
</div>

CSS

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  grid-template-rows: repeat(2, 50px);
}

.container div {
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-size: 28px;
  margin: 1px;
}

.container div:nth-child(2n) {
  background: green;
}

.container div:nth-child(2n-1) {
  background: orange;
}

React

jsx -> (React.createElement) -> 转换

export function createElement(type, config, children) {
  //  ... 
    
  // 工厂函数
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

流程:

  • 将key、ref、__self等属性挂载到props对象上。
  • props.children可以是对象也可以是数组。
  • 返回ReactElement工程函数

通过 JSX 写的 <APP /> 代表着 ReactElementAPP 代表着 React Component。

在写组件的时候需要继承Component或者PureComponent组件,为啥呢?

function Component(props, context, updater){}

通过继承这个组件,我们才有了setStateforceUpdate这些方法。

这些方法是存在于Component原型对象上的。