Nodejs 异步I/O

单线程同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用

多线程编程模型也因为编程中的死锁, 状态同步等问题让开发人员头疼.

Node在两者之间给出了它的方案, 利用单线程, 远离多线程死锁, 状态同步等问题, 利用异步I/O, 让单线程远离阻塞, 以更好的使用CPU

异步I/O可以算做Node的特色 , 因为它是首个大规模将异步I/O应用在应用层上的平台, 它力求在单线程上将资源分配得更高效

为了弥补单线程无法利用多核CPU的缺点, Node提供了类似前端浏览器中Web Workers的子进程, 该子进程可以通过工作进程高效地利用CPU和I/O

Node 自身的执行模型为 事件循环, 正是它使得回调函数十分普遍

在进程启动时, Node便会创建一个类似于while(true)的循环, 每执行一次循环体的过程我们称为Tick. 每个Tick的过程就是查看是否有事件需要处理, 如果有, 就取出事件及其及相关的回调函数. 如果存在相关的回调函数, 就执行它们, 然后进入下一次循环, 如果不再有事件处理, 就退出进程.

观察者

在每个Tick的过程中如何判断是否有事件需要处理呢, 这里必须要引入的概念是观察者

每个事件循环中有一个或者多个观察者, 而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件

事件循环是一种典型的 生产者/消费者 模型,   异步I/O,网络请求等是事件的生产者, 这些事件被传递给观察者, 事件循环则从观察者哪里取出事件并处理.

在windows下, 这个循环基于IOCP创建, 在Unix和Linux下则基于多线程创建

从Javascript发起调用到内核执行完I/O操作的过程中, 存在一种中间产物, 它叫做请求对象

事件循环, 观察者, 请求对象, I/O线程池这四者共同构成了Node异步I/O模型的基本要素

同步式

对于同步式的服务,一次只能处理一个请求, 并且多余请求都处于等待状态

每进程/每请求

为每个请求启动一个进程, 这样可以处理多个请求, 但是它不具备扩展性, 因为系统资源只有那么多

每线程/每请求

为每个请求启动一个线程来处理. 尽管线程比进程要轻量, 但是由于每个线程都占用一定内存, 当大并发请求到来时, 内存将会很快用光, 导致服务器缓慢.

每线程/每请求的方式目前还被Apache所采用. Node通过事件驱动的方式处理请求,无须为每个请求创建额外的对应线程, 可以省掉创建线程和销毁线程的开销, 同时操作系统在调度任务时,因为线程较少, 上下文切换的代价很低. 这使得服务器能够有条不紊地处理请求, 即使在大量连接的情况下,也不受线程上下文切换开销的影响

ECMAScript6 (1)

ES6 中使用let和const 都可以声明变量, 但是它们与var不同

虽然var可以声明局部变量但是无论在哪里声明都会被当成在当前作用域顶部声明的变量, 这就是提升机制

function getValue(condition){

    if(condition){

        var value = "blue";

        return value;

    }else{

        return null;

    }

}

上面的代码编译后等价于

function getValue(condition){

    var value;

    if(condition){

        value = "blue";

        return value;

    }else{

        return null;

    }

}

这个机制通常会导致当condition为true时, 在else块中也可以访问到value变量, 只不过因为它只是通过var value; 声明过并没有进行初始化

所以会得到一个undefined

将上述代码的var替换成let之后,  当condition为true时, else块中是无法访问到value变量的, 离开作用域块之后value会被销毁

同样, let声明的变量不可以重名

const用于声明常量, 一旦声明无法更改, 在声明时必须立即进行初始化

这里实际上和java的final一样, 虽然const声明常量, 但是如果值是一个引用, 你还是可以修改这个引用指向的对象的属性值

Javascript引擎在扫描代码发现变量声明时, 如果遇到var, 就把声明提升到作用域顶部, 如果遇到let或者const, 则会把声明放入临时死区(TDZ, temporal dead zone)

ECMAScript6 (2) 函数

Javascript 中, 无论函数定义中声明了多少形参, 都可以传入任意数量的参数,

也可以在定义函数时添加针对参数数量的处理逻辑, 当已定义的形参无对应的传入参数时为其指定一个默认值

在ECMAScript5 和早期版本中, 你可以通过以下方式创建函数并赋上默认值

function testArguments(a,b,c){
    b = "default b ";
    c = "default c ";
    
    console.log(a + b + c);
}

testArguments("a ");

上面的代码会输出

a default b default c 

在ECMAScript6中, 上面的代码等价于

function testArguments(a,b="default b ",c="default c"){
    console.log(a + b + c);
}

testArguments("a ");

这里重点来看一下箭头函数 

在ECMAScript6中, 箭头函数是一种使用 => 定义函数的新语法, 但是这种方式创建的函数与传统Javascript函数有些许不同

1. 没有this, super, arguments 和 new.target 绑定

箭头函数中的 this, super, arguments 和 new.target 这些值由外围最近一层非箭头函数决定

2. 不能通过new关键字调用

箭头函数没有 construct 方法, 所以不能通过new关键字调用, 程序会抛出错误

3. 没有原型

由于不可以通过new关键字调用箭头函数, 因为没有构建原型的需求, 所以箭头函数不存在prototype这个属性

4. 不可以改变this绑定

函数内部的this值不可以被改变, 在函数的生命周期内始终保持不变

5. 不支持arguments对象

6. 不支持重复命名参数

let sum = (x,y) => x+y;

等价于

let sum = function(x,y){
    return x+y;
}

ECMAScript6 (3) 对象

ECMAScript(5) 中如果为对象添加方法, 必需通过指定名称并完整定义函数来实现, 例如下面的例子

var person = {
    name:"Li",
    sayName: function(){
        console.log(this.name);
    }    
}

person.sayName();

在ECMAScript6中等价于

var person = {
    name:"Li",
    sayName(){
        console.log(this.name);
    }    
}

person.sayName();

对象字面量的语法形式是在一个赋值操作符左边放置一个对象字面量

let student = {
        name:"Li",
        age:"20"
}

let {name,age} = student;

console.log(name);
console.log(age);

ECMAScript6 (4) 类

类的声明

ECMAScript6中有一种与其他语言中类似的类特性: 类声明

同时它也是ECMAScript6中最简单的类形式

要声明一个类

class Student{
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
}

new Student('Li').sayName();

然后强调一下构造器中的this

其值会被绑定到调用this的函数的最近的父对象

var person = {
  first: 'John',
  last: 'Smith',
  full: function() {
    console.log(this.first + ' ' + this.last);
  },
  personTwo: {
    first: 'Allison',
    last: 'Jones',
    full: function() {
      console.log(this.first + ' ' + this.last);
    }
  }
};

person.full();
//输出 'John Smith'
person.personTwo.full();
//输出 'Allison Jones'

这里React构造函数中经常写的

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
}

bind()方法会创建一个新函数,当这个新函数被调用时,它的this值是传递给bind()的第一个参数, 它的参数是bind()的其他参数和其原本的参数. 

也就是说, this.handleClick.bind(this); 这句代码执行之后会创建一个新函数, 然后把这个函数赋值给等号左边的原函数引用( 当然你也可以起名叫 this.newHandleClick )

rendor函数中的 

<button onClick={this.handleClick}> {this.state.isToggleOn ? ‘ON’ : ‘OFF’} </button>

调用的实际上是新函数, 调用这个新函数的this值也就赋给了bind的参数this, 也就传入了handleClick函数的内部

如果去掉这句绑定, 那么构造函数中的this和handleClick函数中的this就成了不同的this

React browserHistory.push 传参

传参方式1:

将参数直接写在路径中,前提是router定义时,需要指定参数的名字,这里假定为:value;

browserHistory.push(pathname: '/routerName/123');

如何获取到123呢?

通过this.props.params.value即可获取

传参方式2:

通过state传参,可以在页面跳转时,url中不显示参数;

browserHistory.push(pathname: '/routerName',state: { key: value });

跳转界面如何接收参数呢?

this.props.location.state.key便可以获得state中的key的value;

尝试了第二种方式发现不好用, 可能是router版本更新到4之后网上的内容太旧了

经过推敲之后发现可以这样

var data = {issueId:60};  
var path = {  
     pathname:’/IssueDetail.html’,  
     state:data,  
}  
this.props.history.push(path);

然后接收的时候要带着withRouter

export default withRouter(IssueDetailDetails);

id:_this.props.location.state.issueId

React ES6 优化笔记

以下内容为最近一段时间使用React的优化笔记

1. 使用解构传递参数

const AddPlayerForm = ({ values, onSubmit, onChange }) => {

    return (…)

}

2. promise函数中不一定要使用_this


          .then(response => {
              if(this.isMountedDone) {
                  this.setState({
                      options: response.data
                  });
              }

3.     Stop Memory Leaks with componentWillUnmount Lifecycle Method in React

    componentDidMount() {
        this.isMountedDone = true;
        if(this.props.loadByDb===”true”){
            this.loadData();
        }

    };
    
    componentWillUnmount(){
        this.isMountedDone = false;
    }

loadData(){
        let url = Global.serverpath+’/api/v1/postlogin/sysparameters’;
         axios.get(url, {
            params: {
              module_key:this.props.module_key,
              value_key:this.props.value_key
            },
            headers: {
              “lira_token”: Global.getCookie(‘lira_token’)
            }
          })
          .then(response => {
              if(this.isMountedDone) {
                  this.setState({
                      options: response.data
                  });
              }
          }).catch(function (error) {
            alert(“load error”+JSON.stringify(error));
          });
    }

4. JSX 中使用if else

{errors.name && <AlertBox>{errors.name}</AlertBox>}

上面语句的语义在于, 先执行errors.name 如果返回为true, 则继续执行<AlertBox>, 而AlertBox实际是一个调用

反之, 使用 || 

{errors.name || <AlertBox>{errors.name}</AlertBox>}

如果errors.name已经返回了true, 则 or 后面的内容会被忽略

5. Component引用与调用

下面的例子是一个对于PlayerForm的引用

<Formik>

{PlayerForm}

</Formik>

下面是调用

<Formik>

    <div>{PlayerForm}</div>

</Formik>

区别在于一个传递的是引用,类似<Test callback={myfunction} />

另一个传递的是调用后的返回值(当然如果是一个方法也没有问题)<Test callback={myfunction()} />

6. 拼接字符串

this.setState({ players: […this.state.players, player] });

React from取值 ( by formik )

使用withFormik

import React from "react";
import { withFormik } from 'formik';

const AddPlayerForm = (props) => {
  const { values, handleSubmit, handleChange } = props;
  console.log(props)
  return (
    <form className="form" onSubmit={handleSubmit}>
      <fieldset className="form-group">
        <label htmlFor="username">Name:</label>
        <input
          name="name"
          type="text"
          onChange={handleChange}
          value={values.name}
          required
          className="form-control"
        />
        <label htmlFor="team">Team:</label>
        <input
          name="team"
          type="text"
          onChange={handleChange}
          value={values.team}
          required
          className="form-control"
        />
      </fieldset>
      <button type="submit" className="btn btn-primary btn-block">
        Save
      </button>
    </form>
  );
};

const EnhancedForm = withFormik({
  handleSubmit: (values, functions) => {
    setTimeout(() => {
      alert(JSON.stringify(values, null, 2));
      functions.props.save(values);
    }, 1000);
  }
})(AddPlayerForm);

export default EnhancedForm;

直接使用Formik标签

import React from "react";
import AlertBox from "./AlertBox";
const AddPlayerForm = ({
  errors,
  touched,
  handleSubmit,
  handleChange,
  values
}) => {
  console.log(errors);
  const handleClick = e => {
    alert("clicked");
  };
  return (
    <form className="form" onSubmit={handleSubmit}>
      <fieldset className="form-group">
        <label htmlFor="username">Name:</label>
        <input
          name="name"
          type="text"
          onChange={handleChange}
          value={values.name}
          required
          className="form-control"
        />
        {errors.name && <AlertBox>{errors.name}</AlertBox>}
        <label htmlFor="team">Team:</label>
        <input
          name="team"
          type="text"
          onChange={handleChange}
          value={values.team}
          required
          className="form-control"
        />
        {errors.team && <AlertBox>{errors.team}</AlertBox>}
      </fieldset>
      <button type="submit" className="btn btn-primary btn-block">
        Save
      </button>
    </form>
  );
};

export default AddPlayerForm;
import React from "react";
import { Formik } from "formik";
import PlayerList from "./components/PlayerList";
import AddPlayerForm from "./components/AddPlayerForm";

class App extends React.Component {
  check(players, name) {
    const duplicate = players.find(player => {
      return player.name === name;
    });
    return duplicate !== undefined
  }

  validate = values => {
    let errors = {};
    if (!values.name) {
      errors.name = "Required";
    }
    if (!values.team) {
      errors.team = "Required";
    }

    if (this.check(this.state.players, values.name)) {
      errors.name = "Duplicated";
    }
    console.log(errors);
    errors.messages = JSON.stringify(errors, null, 2);
    return errors;
  };

  state = {
    players: [{ name: "Jordan", team: "Bulls" }]
  };
  addPlayer = player => {
    this.setState({ players: [...this.state.players, player] });
  };
  render() {
    const { players } = this.state;
    return (
      <div>
        <h3>Player list ({players.length})</h3>
        <PlayerList players={players} />
        <h3>Add a player</h3>
        <Formik
          initialValues={{
            name: "",
            team: ""
          }}
          onSubmit={values => {
            setTimeout(() => {
              alert(JSON.stringify(values, null, 2));
              this.addPlayer(values);
            }, 500);
          }}
          validate={this.validate.bind(this)}
          render={({ errors, touched, handleSubmit, handleChange, values }) => (
            <AddPlayerForm
              errors={errors}
              touched={touched}
              handleSubmit={handleSubmit}
              handleChange={handleChange}
              values={values}
            />
          )}
        />
      </div>
    );
  }
}
export default App;
 

以下补疑一个特殊写法,如何向formik中的一个stateless组件传值

<Formik onSubmit={onSubmit} initialValues={player}>

{formikProps => <EditPlayerForm {...formikProps} iWantTransferthisValue={"great!"} />}

</Formik>
const EditPlayerForm = props => <PlayerForm {...props} isNewPlayer={false} />;
const PlayerForm = ({

isNewPlayer,

values,

handleSubmit,

handleChange,

isSubmitting,

iWantTransferthisValue

}) => {

console.log(iWantTransferthisValue)

return (

)

}

for submit error

const onSubmit = async (values, { props, setSubmitting, setErrors }) => {
    await api.edit(values).then(
      values => {
        console.log(values),
        setSubmitting(false),
        history.push("/players")
      },
      error => {
        setSubmitting(false);
        setErrors({ general: error.message });
      });
  };

下面两种写法实际上是相同的

<Formik>
  {({ myFormikProps }) => <MyForm extraProp={'nice!'} {...myFormikProps} />}
</Formik>
<Formik>
  {withProps({ isNewPlayer: false })(PlayerForm)}
</Formik>

react-testing-library

任意目录下的 *.test.js

__tests__ 目录下的 *.js

import React from 'react'
import {renderIntoDocument, cleanup} from 'react-testing-library'
import App from '../App'
import * as jestDom from 'jest-dom'

expect.extend(jestDom)

afterEach(cleanup)

test('react-testing-library works!', () => {
  const {container} = renderIntoDocument(<App name="Jill" />)
  expect(container.firstChild).toHaveTextContent('Hello Jill!')
})
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App name="Lizhe"/>, document.getElementById('root'));
registerServiceWorker();

npm run test XXXXXX.test.js

Formik 总结

以下内容总结自Formik的官方文档, 所以你会看到一些原文引用(当然是我过滤后的)

Overview

Formik 总的来说帮助我们做下面3件事

  1. Getting values in and out of form state
  2. Validation and error messages
  3. Handling form submission

Why not Redux-Form?

总的来说, redux form提供的东西太重了, 过多的reducer, 而且通常情况下我的Form值并不需要保存在redux的state中

My goal with Formik was to create a scalable, performant, form helper with a minimal API that does the really really annoying stuff and leaves the rest up to you.

Formik 使用以下3个event handlers

handleChange, handleBlur, and handleSubmit 

handleChange and handleBlur work exactly as expected–they use a name or id attribute to figure out which field to update.

也就是说Formik需要使用name或者是id来确认是哪个field被change了

以下两种方式使用Formik都是可以的

There are two ways to use Formik:

  • withFormik(): A Higher-order Component (HoC) that accepts a configuration object
  • <Formik />: A React component with a render prop

正常来说你可以使用下面这些常用属性

  values,

  errors,

  touched,

  handleChange,

  handleBlur,

  handleSubmit,

  isSubmitting,

如果你向我一样, 想传入一些自定义的变量, 你可要看仔细了

自定义的form应该是这样的

import React from "react";

import Alert from "./Alert";

 

const PlayerForm = ({

values,

handleSubmit,

iWantTransferthisValue

}) => {

console.log("here:")

console.log(iWantTransferthisValue)

return (

<form className="form" onSubmit={handleSubmit}>

{iWantTransferthisValue}

</form>

);

};

 

export default PlayerForm;

下面是对上面form的调用

const EditPlayerForm = props => <PlayerForm {...props} isNewPlayer={false} iWantTransferthisValue={props.wantedValue}/>;
<Formik onSubmit={onSubmit} initialValues={player} iWantTransferthisValue="testValue">

            {x => <EditPlayerForm {...x} iWantTransferthisValue={'great!'} /> }

</Formik>

submit方法接收两个参

第一个是values, 保存form的值, 第二个参数是扩展参数, 这里用结构拆开, 包含了一些回调函数

const onSubmit = async (values, { props, setSubmitting, setErrors }) => {
    await api.edit(values).then(
      values => {
        console.log(values),
        setSubmitting(false),
        history.push("/players")
      },
      error => {
        setSubmitting(false);
        setErrors({ general: error.message });
      });
  };

如果使用Field 标签, 还可以写的更简单一些, 下面的input标签和Field标签等价

<input

              type="text"

              name="social.facebook"

              onChange={handleChange}

              onBlur={handleBlur}

              value={values.social.facebook}

/>
<Form>

            <Field type="email" name="email" />

</Form>

如果需要给form中的fileds赋初始值

<Formik

      initialValues={{ name: 'jared' }}

</Formik>