Giter Club home page Giter Club logo

blog's People

Contributors

monsterooo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

prpr2012

blog's Issues

> > @monsterooo

@monsterooo

我同意@AlexZhong22c的说法。

  • 这个依赖是要写的,useCallback只是说当依赖不变的时候,返回同一个函数,但是不能省略依赖的
  • useCallback应该是说避免了其他的state对当前函数的影响。这个count的依赖项我认为是必须要的。
  • 另外呢,还可以用useReducer去维护一个局部的redux,dispatch去做,见useReducer

我看错了, setCount(count => count + 1) setCount是个函数,可以拿到最新的值。相当于以前的setState(prevState => prevState.count + 1)。
不过,useCallback(() => setCount(count => count + 1), [])第二个参数写空数组不合适的吧,毕竟官方文档描述认为函数中引用的项都应该出现在依赖中

最佳实践还是按照官方的来没问题,因为那个比较适合大多数情况。

像我们讨论的这种情况(优化依赖和缓存函数),不是应用特别大或者组件特别多的时候不会有太大的问题。

但是如果我们在写代码之前就知道并且做一些优化,对于以后可能会出现的性能问题就会有所避免。

Originally posted by @monsterooo in #37 (comment)

react 推荐的官方插件,会强制给你把count给你添加上,还有一点gif图,如果不开全局代理,gif图都挂了

React DnD 预览

React DnD
GitHub - mzabriskie/react-draggable: React draggable component

预览

  • DnD在内部使用Redux保存数据

  • 后端(Backends):
    在DnD中你可以使用可插拔的方法去实现不同平台的拖动功能,在web上一般使用HTML5的drag drop,这种可插拔的方式在DnD中叫做后端。

  • 项目和类型(Items and Types):
    在DnD中使用数据而不是视图作为真实的来源,当在屏幕上拖动某些内容的时候,我们不会说正在拖动组件或DOM节点。相反,我们说某个类型(type)的项目(item)正在被拖动。

什么是项目(item)?项目(item)是一个简单的javascript对象,用于描述被拖动的内容。例如在看板应用程序中,当你拖动卡片是,项目可能看起来像{cardId: 42}。将拖动的数据描述为普通的对象有助于保持组件分离并且彼此不知道

  • 监视器(Monitors):
    拖动本质上是有状态的。要么拖动操作正在进行,要么没有。要么有当前类型(type)和当前项目(item),要么没有。这种state必须存在某个地方。

DnD通过称为监视器(monitors)的内部状态存储上的几个小包装器将此状态公开给组件。监视器允许你更新组件的poprs以相应拖动状态更改。

假设你想在拖动棋子时突出显示象棋单元格。Cell组件的收集函数(collecting function)可能就像下面这样:

function collect(monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver(),
  }
}

它指示DnD将突出显示的最新值作为props传递给所有Cell实例

  • 连接器(Connectors):
    如果后端处理DOM事件,但是组件使用React来描述DOM,name后端你如何指定要侦听那些DOM节点呢?使用连接器(connectors)。连接器(connectors)允许你讲预定义的角色(拖动源(drag source)、拖动预览(drag preview)或放置目标(drop target))分配给渲染功能中的DOM节点。

实际上,连接器(connectors)作为我们上面描述的收集函数的第一个参数传递。让我们来看看如何使用它来指定放置目标

// collect函数的第一个参数就是连接器
function collect(connect, monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver(),
    connectDropTarget: connect.dropTarget(),
  }
}

在组件render方法中,我们即可以访问从监视器(monitor)获得的数据和从连接器(connector)获得的函数:

render() {
  const { highlighted, hovered, connectDropTarget } = this.props;

  return connectDropTarget(
    <div className={classSet({
      'Cell': true,
      'Cell--highlighted': highlighted,
      'Cell--hovered': hovered
    })}>
      {this.props.children}
    </div>
  );
}

connectDropTarget调用告诉DnD我们的根DOM节点是一个有效的放置(drop)目标,并且它的hover和drop事件应该由后端处理。在内部,它通过将一个回调引用附加到你给它的React元素来工作。连接器返回的函数是memoized,因此它不会破坏shouldComponentUpdate优化。

  • 拖动源和放置目标(Drag Sources and Drop Targets)
    每当你想要使组件或其某部分可拖动时,你需要将该组件包装到拖动源(drag source)申明中。每个拖动都注册了某种类型(type),并且必须实现从组件的props中生成项目(item)的方法。它还可以选择指定一些其他方法来处理拖放时间。拖动源(drag source)声明还允许你指定给定组件的收集函数(collecting function)

  • 高阶组件(Higher-Order)和装饰器(Decorators)
    你如何包装组件?包装是什么意思?如果你以前没有使用过高阶组件,请继续阅读本文,因为它详细解释了这个概念。

高阶组件组件只是一个函数,它接收一个React组件,并返回另一个React组件类。库提供的包装组件在其render方法中呈现组将并将props转发给它,但也添加了一些有用的行为。

在DnD中,DragSourceDragTarget以及一些其他顶级导出函数实际上是高阶组件。它们向组件中注入拖放魔法。

使用它们的一个提示是,它们必须需要两个函数。例如,一下是如何在DragSource中包装YourComponent

import { DragSource } from 'react-dnd'

class YourComponent {
  /* ... */
}

export default DragSource(/* ... */)(YourComponent)

请注意,在第一个函数调用中指定DragSource参数之后,还有第二个函数调用,第二个函数调用中传递你的类(组件)。这叫做curryingpartial application,并且是装饰器语法开箱即用的必要条件:

import { DragSource } from 'react-dnd'

@DragSource(/* ... */)
export default class YourComponent {
  /* ... */
}

你不需要使用这种装饰器语法,如果你喜欢它,你可以使用Babel来转换你的代码,并将{ "stage": 1 }放入.babelrc文件来启用它。

即使你不打算使用装饰器,部分应用程序仍然可以使用,因为你可以使用如_.flow在javascript中组合多个DragSourceDropTarget声明。

import { DragSource, DropTarget } from 'react-dnd'
import flow from 'lodash/flow'

class YourComponent {
  render() {
    const { connectDragSource, connectDropTarget } = this.props
    return connectDragSource(
      connectDropTarget(),
      /* ... */
    )
  }
}

export default flow(
  DragSource(/* ... */),
  DropTarget(/* ... */),
)(YourComponent)
  • 将他们放在一起
    下面是将现有的Card组件包装到拖动源中的示例。
import React from 'react'
import { DragSource } from 'react-dnd'

// Drag sources和drag targets只有在具有相同的字符串类型是才交互
const Types = {
  CARD: 'card',
}

/**
 * 指定drag source 合约
 * 只需要 `beginDrag` 功能
 */
const cardSource = {
  beginDrag(props) {
    // 返回描述拖动项目(item)的数据
    const item = { id: props.id }
    return item
  },

  endDrag(props, monitor, component) {
    if (!monitor.didDrop()) {
      return
    }

    // 做一些拖动操作处理
    const item = monitor.getItem()
    const dropResult = monitor.getDropResult()
    CardActions.moveCardToList(item.id, dropResult.listId)
  },
}

/**
 * 指定要注入到组件的props
 */
function collect(connect, monitor) {
  return {
    // 在render()中调用此函数,让DnD处理拖动事件
    connectDragSource: connect.dragSource(),
    // 你还可以通过监视器获取当前拖动状态
    isDragging: monitor.isDragging(),
  }
}

function Card(props) {
  // 你的组件可以像以前一样收到props
  const { id } = props

  // 这两个props由DnD注入,由上面的`collect`函数定义
  const { isDragging, connectDragSource } = props

  return connectDragSource(
    <div>
      I am a draggable card number {id}
      {isDragging && ' (and I am being dragged now)'}
    </div>,
  )
}

// 导出包装的版本
export default DragSource(Types.CARD, cardSource, collect)(Card)

本章预览部分完。

recompose withHandlers

withHandlers 介绍

withhandlers接收一个对象函数为其接下来的组件创建事件处理函数,每个事件处理函数都接收一个参数,这个参数就是组件的props

withHandlers Flow Type

withHandlers(
  handlerCreators: {
    [handlerName: string]: (props: Object) => Function
  } |
  handlerCreatorsFactory: (initialProps) => {
    [handlerName: string]: (props: Object) => Function
  }
): HigherOrderComponent

实际调用也就是两种形式

// 1
withHandlers({
   handleClick: props => event => {
      alert(props.title)
   }
})
// 2
withHandlers(() => {
    return {
      handleClick: props => event => {
         console.log('我被点击了', props);
      },
    };
  })

withHandlers 实例

const { compose, withHandlers } = Recompose;
const ButtonEnhance = compose(
  withHandlers({
    handleClick: props => event => {
      alert('我被点击了, ' + props.title)
    }
  })
)(({ handleClick }) => (
  <button onClick={handleClick}>点我~~</button>
))

在线DEMO

codepen在线预览

recompose renderComponent

renderComponent 介绍

renderComponent用于包装一个组件为高阶函数,当一个组件需要成为高阶函数和其他高阶函数组合时比较有用,比如和branch组合

renderComponent Flow Type

renderComponent(
  Component: ReactClass | ReactFunctionalComponent | string
): HigherOrderComponent

renderComponent 例子

const { compose, branch, renderComponent } = Recompose;

const Bar1 = () => (<p>Bar1</p>);
const Bar2 = () => (<p>Bar2</p>);

const Foo = compose(
  branch(
    props => props.isShow,
    renderComponent(Bar1),
    renderComponent(Bar2),
  ),
)(({ title }) => (
  <div>{ title }</div>
))

在线DEMO

codepen在线预览

> > @monsterooo

@monsterooo

我同意@AlexZhong22c的说法。

  • 这个依赖是要写的,useCallback只是说当依赖不变的时候,返回同一个函数,但是不能省略依赖的
  • useCallback应该是说避免了其他的state对当前函数的影响。这个count的依赖项我认为是必须要的。
  • 另外呢,还可以用useReducer去维护一个局部的redux,dispatch去做,见useReducer

我看错了, setCount(count => count + 1) setCount是个函数,可以拿到最新的值。相当于以前的setState(prevState => prevState.count + 1)。
不过,useCallback(() => setCount(count => count + 1), [])第二个参数写空数组不合适的吧,毕竟官方文档描述认为函数中引用的项都应该出现在依赖中

最佳实践还是按照官方的来没问题,因为那个比较适合大多数情况。

像我们讨论的这种情况(优化依赖和缓存函数),不是应用特别大或者组件特别多的时候不会有太大的问题。

但是如果我们在写代码之前就知道并且做一些优化,对于以后可能会出现的性能问题就会有所避免。

Originally posted by @monsterooo in #37 (comment)

react 推荐的官方插件,会强制给你把count给你添加上

recompose withProps

withProps介绍

withProps接受一个函数参数,这个函数参数会返回一个对象用作为接下来的组件的props。与mapProps不同的是,除了withProps函数参数返回的props外,组件接收到的其他参数也将一起被传递

withProps Flow Type

withProps(
  createProps: (ownerProps: Object) => Object | Object
): HigherOrderComponent

withProps实例

const ListMap = withProps(({ list }) => {
  return {
    list: list.map((e) => e + '_withProps')
  };
})(List);
// title 也会被一同传递到List组件中
<ListMap list={Item} title="我是一个标题文本!" />

在线DEMO

codepen在线预览

recompose mapProps

mapProps介绍

mapProps函数接收一个函数参数,这个函数参数会返回一个对象用作为接下来的组件的props。组件接收到的props只能是通过mapProps函数参数返回的对象,其他的props将会被忽略

mapProps 实例

const Item = ['a', 'b', 'c', 'd'];
const ListMap = mapProps(({ list }) => {
  return {
    list: list.map((e) => e + '_extends')
  };
})(List);

现在可以调用ListMap组件,并且可以给它传递一个list属性<ListMap list={Item} />,在List组件中获取到的list数组值每个后缀都会被加上_extends字符。并且如果你还有其他额外的props传入会被过滤掉比如<ListMap list={Item} title="hello"/>,在List组件中并不会接收到title属性。

在线DEMO

codepen在线预览

算法练习

算法练习代码

练习地址: https://coderlane.net/s

排序分类

选择排序

function Solution() {
  var arr = [5,3,1,8,7,0,2,6];
  var N = arr.length;
  for(var i = 0; i < N; i++) {
    var minIdx = i;
    for(var j = i + 1; j < N; j++) {
      if (arr[j] < arr[minIdx]) { // 选择最小的元素
        minIdx = j;
      }
    }
    exch(arr, i, minIdx);
  }
  console.log('排序完成:', arr)
}
function exch(arr, i, j) {
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
Solution();

插入排序

function Solution() {
  var arr = [1,7,2,4,8,0];
  var N = arr.length;
  for(var i = 1; i < N; i++) {
    var cur = arr[i];
    var j = i - 1;
    while(j >= 0 && arr[j] > cur) {
      arr[j+1] = arr[j];
      --j;
    }
    arr[j+1] = cur;
  }
  console.log('插入排序方式1', arr)
}
Solution();

recompose renderNothing

renderNothing 介绍

renderNothing和它的名字一样,它是一个返回null的高阶组件

renderNothing Flow Type

renderNothing: HigherOrderComponent

renderNothing 例子

const { compose, branch, renderNothing } = Recompose;

const Bar = () => (<p>Bar</p>);

const Foo = compose(
  branch(
    props => props.isShow,
    () => Bar,
    renderNothing,
  ),
)(({ title }) => (
  <div>{ title }</div>
))

class App extends React.Component {
  render() {
    return (
      <div>
        什么组件也不展示
        <Foo isShow={false} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'))

在线DEMO

codepen在线预览

关于React createFactory函数

官方解释
返回一个函数,这个函数可以产生你给定类型的React元素。像React.createElement(),参数类型可以是div、span、一个React Component(类或函数),或是一个React.fragment

官方说的有点含糊并且也没有提供一些代码示例,下面就来总结一下这两个的使用方法和区别

  • React.createElement()
React.createElement(
  type,
  [props],
  [...children]
)

这个函数用法很明显它创建一个React元素,并且接收三个参数。第一个参数为类型包括html元素类型和React Component或React fragment。第二个参数是元素的属性对应的是html的属性,第三个参数为子元素,可以为一个字符串。也可以是React Component

  • React.createFactory()
React.createFactory(type)

createFactory底层还是调用了createElement,不同的是createFactory返回的是一个函数。它不会立刻调用createElement。而是在你调用这个返回函数的时候创建元素(HOC)

举个例子

const factory = React.createFactory('div');
const TestCreateFactory = factory(null, 'div create factory');

上面factory函数是调用createFactory返回的一个函数,调用factory函数创建的所有组件必然是一个html的div元素,factory(null, 'div create factory')可以为创建的div元素指定属性和子元素。

总结

React.createElement立刻创建一个React组件
React.createFactory返回一个工厂函数,调用它可以创建一系列相同的组件。

recompose withState

withState 介绍

withState可以为组件增加一个state和一个set state的函数,withState接收三个参数,第一个参数为state name,第二个参数是set state的函数名,第三个参数是一个初始值。

set state函数有两种调用方式,具体类型如下:

stateUpdater<T>((prevValue: T) => T, ?callback: Function): void
stateUpdater(newValue: any, ?callback: Function): void

withState Flow Type

withState(
  stateName: string,
  stateUpdaterName: string,
  initialState: any | (props: Object) => any
): HigherOrderComponent

withState 实例

const { compose, withState } = Recompose;

const Foo = compose(
  withState('title', 'setTitle', "我是默认字符串!")
)(({ title, setTitle }) => (
  <div>
    <p>{title}</p>
    <button onClick={() => setTitle('Hello,World!')}>点击我更改标题</button>
    <button onClick={() => setTitle((prevValue) => `${prevValue},Hello,World!`)}>点击我也可以更改标题</button>
  </div>
))

备注

withState最好与withHandlers一起使用,避免创建() => setTitle('Hello,World!')这样的匿名函数

在线DEMO

codepen在线预览

React Render Props 模式

概述

Render Props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力,接下来我们一步一步来看React组件中如何实现这样的功能。

React 组件数据传递

React中我们可以给一个组件传递一些props并且在组件内部展示,同样的我们也可以传递一些组件同样也是行得通的,一起看一个例子

1. 组件普通数据传递

我们可以通过组件传递一些字符串数据,并且在组件内部渲染
下面的代码很平常,我们绝大多数代码都是这样。

const Foo = ({ title }) => (
  <div>
    <p>{title}</p>
  </div>
);
class App extends React.Component {
  render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo title="大家好,我是土豆" />
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

2. 组件上传递组件

更进一步,我们可以在组件上传递普通的HTML 标签React 组件达到复用的目的

// https://codepen.io/tudou/full/OvdrPW
const Bar = () => (<p>我是Bar组件 :)</p>);
const Foo = ({ title, component }) => (
  <div>
    <p>{title}</p>
    {component()}
  </div>
);
class App extends React.Component {
  render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo title={<p>大家好,我是土豆</p>} component={() => <Bar /> } />
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

在上面的例子中传递普通的HTML 标签对我们复用组件没有任何帮助,重点可以看传递component这个参数,它传递给Foo组件一个函数这个函数返回的是一个Bar 组件,我们会在Foo 组件中调用并且显示component函数中的组件。我们再来写一个小的DEMO进行验证。

3. 一个纯粹的Render Props例子

// https://codepen.io/tudou/full/dmawvY
const Bar = ({ title }) => (<p>{title}</p>);

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: '我是一个state的属性' };
  }
  render() {
    const { render } = this.props;
    const { title } = this.state;
    
    return (
      <div>
        {render(title)}
      </div>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo render={(title) => <Bar title={title} />} />
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

在上面的例子中,给Foo 组件传递了一个render参数它是一个函数这个函数返回一个Bar组件,这个函数接受一个参数title他来自于Foo 组件调用时传递并且我们又将title 属性传递给了Bar 组件。经过上述的调用过程我们的Bar 组件就可以共享到Foo 组件内部的state 属性`。

4. 通过children传递

这个demo略微不同于上面通过props传递,而它是通过组件的children传递一个函数给Foo 组件

// https://codepen.io/tudou/full/WzPPeL
const Bar = ({ title }) => (<p>{title}</p>);

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: '我是一个state的属性' };
  }
  render() {
    const { children } = this.props;
    const { title } = this.state;
    
    return (
      <div>
        {children(title)}
      </div>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo>
          {(title) => (
            <Bar title={title} />
          )}
        </Foo>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

观察可发现只是写法略微有些变法,我们将要传递的数据放到的组件的children。实际上并无不同之处(都是传递一个函数)

<Foo>
  {(title) => (
    <Bar title={title} />
  )}
</Foo>

注意事项

请注意当我们的Foo 组件继承于React.PureComponent的时候,我们需要避免下面这样的写法。不然我们的性能优化将付之东流。

render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo render={(title) => <Bar title={title} />} />
      </div>
    );
  }

如果你在render创建一个函数,在每次渲染的时候render prop将会是一个新的值,那么每次将会重新渲染Bar

正确的做法应该是在组件内部创建一个函数用于显示组件

const Bar = ({ title }) => (<p>{title}</p>);

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: '我是一个state的属性' };
  }
  render() {
    const { render } = this.props;
    const { title } = this.state;
    
    return (
      <div>
        {render(title)}
      </div>
    )
  }
}

class App extends React.Component {
  // 单独创建一个渲染函数
  renderFoo(title) {
    return <Bar title={title} />;
  }
  render() {
    return (
      <div>
        <h2>这是一个示例组件</h2>
        <Foo render={this.renderFoo} />
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

总结

学习了解Render Props渲染模式原理,使用了renderchildren两种不同的渲染方法。更新详细的官方例子请参考 https://reactjs.org/docs/render-props.html
官方例子在线参考 https://codesandbox.io/embed/1075p1yov3

谢谢阅读

掌握Node.js中的Async和Await

在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise.

异步语言结构在其他语言中已经存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了。

Node中的async函数是什么?

当函数声明为一个Async函数它会返回一个AsyncFunction对象,它们类似于Generator因为执可以被暂停。唯一的区别是它们返回的是Promise而不是{ value: any, done: Boolean }对象。不过它们还是非常相似,你可以使用co包来获取同样的功能。

在async函数中,可以等待Promise完成或捕获它拒绝的原因。

如果你要在Promise中实现一些自己的逻辑的话

function handler (req, res) {
  return request('https://user-handler-service')
    .catch((err) => {
      logger.error('Http error', err)
      error.logged = true
      throw err
    })
    .then((response) => Mongo.findOne({ user: response.body.user }))
    .catch((err) => {
      !error.logged && logger.error('Mongo error', err)
      error.logged = true
      throw err
    })
    .then((document) => executeLogic(req, res, document))
    .catch((err) => {
      !error.logged && console.error(err)
      res.status(500).send()
    })
}

可以使用async/await让这个代码看起来像同步执行的代码

async function handler (req, res) {
  let response
  try {
    response = await request('https://user-handler-service')  
  } catch (err) {
    logger.error('Http error', err)
    return res.status(500).send()
  }

  let document
  try {
    document = await Mongo.findOne({ user: response.body.user })
  } catch (err) {
    logger.error('Mongo error', err)
    return res.status(500).send()
  }

  executeLogic(document, req, res)
}

在老的v8版本中,如果有有个promise的拒绝没有被处理你会得到一个警告,可以不用创建一个拒绝错误监听函数。然而,建议在这种情况下退出你的应用程序。因为当你不处理错误时,应用程序处于一个未知的状态。

process.on('unhandledRejection', (err) => { 
  console.error(err)
  process.exit(1)
})

async函数模式

在处理异步操作时,有很多例子让他们就像处理同步代码一样。如果使用Promisecallbacks来解决问题时需要使用很复杂的模式或者外部库。

当需要再循环中使用异步获取数据或使用if-else条件时就是一种很复杂的情况。

指数回退机制

使用Promise实现回退逻辑相当笨拙

function requestWithRetry (url, retryCount) {
  if (retryCount) {
    return new Promise((resolve, reject) => {
      const timeout = Math.pow(2, retryCount)
 
      setTimeout(() => {
        console.log('Waiting', timeout, 'ms')
        _requestWithRetry(url, retryCount)
          .then(resolve)
          .catch(reject)
      }, timeout)
    })
  } else {
    return _requestWithRetry(url, 0)
  }
}

function _requestWithRetry (url, retryCount) {
  return request(url, retryCount)
    .catch((err) => {
      if (err.statusCode && err.statusCode >= 500) {
        console.log('Retrying', err.message, retryCount)
        return requestWithRetry(url, ++retryCount)
      }
      throw err
    })
}

requestWithRetry('http://localhost:3000')
  .then((res) => {
    console.log(res)
  })
  .catch(err => {
    console.error(err)
  })

代码看的让人很头疼,你也不会想看这样的代码。我们可以使用async/await重新这个例子,使其更简单

function wait (timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, timeout)
  })
}

async function requestWithRetry (url) {
  const MAX_RETRIES = 10
  for (let i = 0; i <= MAX_RETRIES; i++) {
    try {
      return await request(url)
    } catch (err) {
      const timeout = Math.pow(2, i)
      console.log('Waiting', timeout, 'ms')
      await wait(timeout)
      console.log('Retrying', err.message, i)
    }
  }
}

上面代码看起来很舒服对不对

中间值

不像前面的例子那么吓人,如果你有3个异步函数依次相互依赖的情况,那么你必须从几个难看的解决方案中进行选择。

functionA返回一个Promise,那么functionB需要这个值而functioinC需要functionAfunctionB完成后的值。

方案1:then 圣诞树

function executeAsyncTask () {
  return functionA()
    .then((valueA) => {
      return functionB(valueA)
        .then((valueB) => {          
          return functionC(valueA, valueB)
        })
    })
}

用这个解决方案,我们在第三个then中可以获得valueAvalueB,然后可以向前面两个then一样获得valueAvalueB的值。这里不能将圣诞树(毁掉地狱)拉平,如果这样做的话会丢失闭包,valueAfunctioinC中将不可用。

方案2:移动到上一级作用域

function executeAsyncTask () {
  let valueA
  return functionA()
    .then((v) => {
      valueA = v
      return functionB(valueA)
    })
    .then((valueB) => {
      return functionC(valueA, valueB)
    })
}

在这颗圣诞树中,我们使用更高的作用域保变量valueA,因为valueA作用域在所有的then作用域外面,所以functionC可以拿到第一个functionA完成的值。

这是一个很有效扁平化.then链"正确"的语法,然而,这种方法我们需要使用两个变量valueAv来保存相同的值。

方案3:使用一个多余的数组

function executeAsyncTask () {
  return functionA()
    .then(valueA => {
      return Promise.all([valueA, functionB(valueA)])
    })
    .then(([valueA, valueB]) => {
      return functionC(valueA, valueB)
    })
}

在函数functionAthen中使用一个数组将valueAPromise一起返回,这样能有效的扁平化圣诞树(回调地狱)。

方案4:写一个帮助函数

const converge = (...promises) => (...args) => {
  let [head, ...tail] = promises
  if (tail.length) {
    return head(...args)
      .then((value) => converge(...tail)(...args.concat([value])))
  } else {
    return head(...args)
  }
}

functionA(2)
  .then((valueA) => converge(functionB, functionC)(valueA))

这样是可行的,写一个帮助函数来屏蔽上下文变量声明。但是这样的代码非常不利于阅读,对于不熟悉这些魔法的人就更难了。

使用async/await我们的问题神奇般的消失

async function executeAsyncTask () {
  const valueA = await functionA()
  const valueB = await functionB(valueA)
  return function3(valueA, valueB)
}

使用async/await处理多个平行请求

和上面一个差不多,如果你想一次执行多个异步任务,然后在不同的地方使用它们的值可以使用async/await轻松搞定。

async function executeParallelAsyncTasks () {
  const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
  doSomethingWith(valueA)
  doSomethingElseWith(valueB)
  doAnotherThingWith(valueC)
}

数组迭代方法

你可以在mapfilterreduce方法中使用async函数,虽然它们看起来不是很直观,但是你可以在控制台中实验以下代码。

1.map
function asyncThing (value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), 100)
  })
}

async function main () {
  return [1,2,3,4].map(async (value) => {
    const v = await asyncThing(value)
    return v * 2
  })
}

main()
  .then(v => console.log(v))
  .catch(err => console.error(err))
2.filter
function asyncThing (value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), 100)
  })
}

async function main () {
  return [1,2,3,4].filter(async (value) => {
    const v = await asyncThing(value)
    return v % 2 === 0
  })
}

main()
  .then(v => console.log(v))
  .catch(err => console.error(err))
3.reduce
function asyncThing (value) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), 100)
  })
}

async function main () {
  return [1,2,3,4].reduce(async (acc, value) => {
    return await acc + await asyncThing(value)
  }, Promise.resolve(0))
}

main()
  .then(v => console.log(v))
  .catch(err => console.error(err))
解决方案:
  1. [ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]

  2. [ 1, 2, 3, 4 ]

  3. 10

如果是map迭代数据你会看到返回值为[ 2, 4, 6, 8 ],唯一的问题是每个值被AsyncFunction函数包裹在了一个Promise

所以如果想要获得它们的值,需要将数组传递给Promise.All()来解开Promise的包裹。

main()
  .then(v => Promise.all(v))
  .then(v => console.log(v))
  .catch(err => console.error(err))

一开始你会等待Promise解决,然后使用map遍历每个值

function main () {
  return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}

main()
  .then(values => values.map((value) => value * 2))
  .then(v => console.log(v))
  .catch(err => console.error(err))

这样好像更简单一些?

如果在你的迭代器中如果你有一个长时间运行的同步逻辑和另一个长时间运行的异步任务,async/await版本任然常有用

这种方式当你能拿到第一个值,就可以开始做一些计算,而不必等到所有Promise完成才运行你的计算。尽管结果包裹在Promise中,但是如果按顺序执行结果会更快。

关于filter的问题

你可能发觉了,即使上面filter函数里面返回了[ false, true, false, true ]await asyncThing(value)会返回一个promise那么你肯定会得到一个原始的值。你可以在return之前等待所有异步完成,在进行过滤。

Reducing很简单,有一点需要注意的就是需要将初始值包裹在Promise.resolve

重写基于callback的node应用成

Async函数默认返回一个Promise ,所以你可以使用Promises来重写任何基于callback的函数,然后await等待他们执行完毕。在node中也可以使用util.promisify函数将基于回调的函数转换为基于Promise的函数

重写基于Promise的应用程序

要转换很简单,.then将Promise执行流串了起来。现在你可以直接使用`async/await。

function asyncTask () {
  return functionA()
    .then((valueA) => functionB(valueA))
    .then((valueB) => functionC(valueB))
    .then((valueC) => functionD(valueC))
    .catch((err) => logger.error(err))
}
 

转换后

async function asyncTask () {
  try {
    const valueA = await functionA()
    const valueB = await functionB(valueA)
    const valueC = await functionC(valueB)
    return await functionD(valueC)
  } catch (err) {
    logger.error(err)
  }
}
Rewriting Nod

使用Async/Await将很大程度上的使应用程序具有高可读性,降低应用程序的处理复杂度(如:错误捕获),如果你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。

如有错误麻烦留言告诉我进行改正,谢谢阅读

原文链接

recompose branch

branch 介绍

branch方法接收三个函数参数,第一个函数返回一个布尔值如果为true则调用branch第二个函数,如果为false则调用branch第三个函数。第二个和第三个为高阶函数,在函数里面可以为组件增强功能。

branch Flow Type

branch(
  test: (props: Object) => boolean,
  left: HigherOrderComponent,
  right: ?HigherOrderComponent
): HigherOrderComponent

branch 实例

const { compose, branch, withProps } = Recompose;

const Foo = compose(
  branch(
    props => props.isShow,
    withProps({ title: '我被显示了' }),
    withProps({ title: '你看不到我' }),
  ),
)(({ title }) => (
  <div>{ title }</div>
))

在线DEMO

codepen在线预览

recompose withReducer

withReducer 介绍

withReducer类似于withState(),但是它的更新模式使用了redux相通的方法。withReducer接收4个参数,第一个参数为state 名称,第二个参数为dispatch 方法名,第三个参数为state 更新函数,第四个参数为初始化状态。withReducerwithState是同样的操作,他们的数据都是保存在代理组件的state中。再向外暴露一个更新的方法。

withReducer Flow Type

withReducer<S, A>(
  stateName: string,
  dispatchName: string,
  reducer: (state: S, action: A) => S,
  initialState: S | (ownerProps: Object) => S
): HigherOrderComponent

withReducer 实例

const { compose, withReducer } = Recompose;

const Foo = compose(
  withReducer('state', 'dispatch', (state, action) => {
    if(action.type === 'SETCOUNTER') {
      return { ...state, counter: action.value };
    }
    return state;
  }, { counter: 0 }),
)(({ state: { counter }, dispatch }) => (
  <div>
    <p>Counter: {counter}</p>
    <button onClick={() => dispatch({ type: 'SETCOUNTER', value: 10 })}>设置Counter</button>
  </div>
))

在线DEMO

codepen在线预览

recompose withPropsOnChange

withPropsOnChange 介绍

withPropsOnChange接收两个参数,第一个参数指定了那些props被更改后需要重新调用计算函数,可以是一个数组里面包含了指定props的key,也可以包含一个自定义函数做一些逻辑后返回一个布尔值。第二个参数也是一个函数参数是props,返回一个对象它是组件的props,其他props也会传递下来,类似于withProps

withPropsOnChange Flow Type

withPropsOnChange(
  shouldMapOrKeys: Array<string> | (props: Object, nextProps: Object) => boolean,
  createProps: (ownerProps: Object) => Object
): HigherOrderComponent

withProps实例

const TriangleArea = withPropsOnChange(
  ['a', 'h'],
  (props) => ({
    area: props.a * props.h / 2,
  })
)(({ area, title = '' }) => (
  <p>{title}{area}</p>
))

只有当TriangleArea组件中props.aprops.h被改变时才会计算area,这样有助于提高性能

在线DEMO

codepen在线预览

recompose onlyUpdateForPropTypes

onlyUpdateForPropTypes 介绍

onlyUpdateForPropTypesonlyUpdateForKeys相似哈,但是它不通过指定props来判断更新,它是根据组件的propTypes来判断更新,当一个组件没有指定任何propTypes时组件不会有任何更新。

onlyUpdateForPropTypes Flow Type

onlyUpdateForPropTypes: HigherOrderComponent

onlyUpdateForPropTypes 例子

const { compose, pure, setPropTypes, onlyUpdateForPropTypes } = Recompose;

const Foo = compose(
  pure,
  onlyUpdateForPropTypes,
  setPropTypes({
    title: PropTypes.string.isRequired,
  }),
)(({ title }) => (
  <div>{console.log('render')}{title}</div>
))

在线DEMO

codepen在线预览

recompose lifecycle

lifecycle 介绍

lifecycleReact Component API的高阶函数版本,通过它可以覆盖React原来的方法除了render函数外。

lifecycle Flow Type

lifecycle(
  spec: Object,
): HigherOrderComponent

lifecycle 例子

const { compose, pure, lifecycle } = Recompose;

const Foo = compose(
  pure,
  lifecycle({
    componentDidMount() {
      alert('组件已被加载');
    }
  }),
)(() => (
  <div>Hello,World!</div>
));

在线DEMO

codepen在线预览

useCallback、useMemo 分析 & 差别

结论

先说结论useCallbackuseMemo都可缓存函数的引用或值,但是从更细的使用角度来说useCallback缓存函数的引用,useMemo缓存计算数据的值。

回顾

useCallback回顾

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

根据官网文档的介绍我们可理解:在ab的变量值不变的情况下,memoizedCallback的引用不变。即:useCallback的第一个入参函数会被缓存,从而达到渲染性能优化的目的。

useMemo回顾

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

根据官方文档的介绍我们可理解:在ab的变量值不变的情况下,memoizedValue的值不变。即:useMemo函数的第一个入参函数不会被执行,从而达到节省计算量的目的。

分析

useCallback分析

我做了这样一个简单示例,我们来分析一下现象。

// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function App() {
  const [count, setCount] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const handleCount = () => setCount(count + 1);
  const handleTotal = () => setTotal(total + 1);
  const prevHandleCount = usePrevProps(handleCount);
  
  console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
  
  return (
    <div>
      <div>Count is {count}</div>
       <div>Total is {total}</div>
      <br/>
      <div>
        <button onClick={handleCount}>Increment Count</button>
        <button onClick={handleTotal}>Increment Total</button>
      </div>
    </div>
  )
}

ReactDOM.render(<App />, document.body)

我们重点看这一行

const handleCount = () => setCount(count + 1);

根据我们之前的理解,我们知道每次App组件渲染时这个handleCount都是重新创建的一个新函数。

const prevHandleCount = usePrevProps(handleCount);  
console.log('两次处理函数是否相等:', prevHandleCount === handleCount);

我们也可以通过比较上一次的prevHandleCount 和本次的handleCount。可以明确的知道每次渲染时handleCount 都是重新创建的一个新函数。

问题:它有什么问题呢?当我们将handleCount作为props传递给其他组件时会导致像PureComponentshouldComponentUpdateReact.memo等相关优化失效(因为每次都是不同的函数)

展示问题Gif:

1

为了解决上述的问题,我们需要引入useCallback,通过使用它的依赖缓存功能,在合适的时候将handleCount缓存起来。我创建了一个简单示例,来看看是如何解决的吧。

// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function App() {
  const [count, setCount] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const handleCount = React.useCallback(() => setCount(count => count + 1), []);
  const handleTotal = () => setTotal(total + 1);
  const prevHandleCount = usePrevProps(handleCount);
  
  console.log('两次处理函数是否相等:', prevHandleCount === handleCount);
  
  return (
    <div>
      <div>Count is {count}</div>
       <div>Total is {total}</div>
      <br/>
      <div>
        <button onClick={handleCount}>Increment Count</button>
        <button onClick={handleTotal}>Increment Total</button>
      </div>
      <AotherComponent onClick={handleCount} />
    </div>
  )
}

const AotherComponent = React.memo(function AotherComponent({ onClick }) {
  console.log('AotherComponent 组件渲染');
  return (
    <button onClick={onClick}>AotherComponent - Inrement Count</button>
  )
})

ReactDOM.render(<App />, document.body)

这次我们重点看这行

const handleCount = React.useCallback(() => setCount(count => count + 1), []);

我使用useCallback来缓存了函数,依赖项(deps)是一个空数组它代表这个函数在组件的生成周期内会永久缓存

const AotherComponent = React.memo(function AotherComponent({ onClick }) {
  console.log('AotherComponent 组件渲染');
  return (
    <button onClick={onClick}>AotherComponent - Inrement Count</button>
  )
})

因为我们的handleCount是一个缓存函数,所以当我们传递给经过React.memo优化的组件AotherComponent时不会触发渲染

温馨提示:在选择useCallback的依赖(deps)时请经过仔细的考虑,比如下面这样的依赖是达不到最好的优化效果,因为当我们增加了一次count时,handleCount的引用就会更改。

解决问题Gif,我们可以看到prevHandleCount等于handleCount,也没有多余的渲染:

2

useMemo分析

useMemouseCallback几乎是99%像是,当我们理解了useCallback后理解useMemo就非常简单。

他们的唯一区别就是:useCallback是根据依赖(deps)缓存第一个入参的(callback)。useMemo是根据依赖(deps)缓存第一个入参(callback)执行后的值。

你明白了吗? 如果还没明白我贴一下useCallbackuseMemo的源码你来看看区别。

// 注:为了方便理解我省去了一些flow语法

function updateCallback(callback, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate(); // 🤩
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

聪明的你一定看出来区别是啥了对吧。

useMemo一般用于密集型计算大的一些缓存。

下面我写了一个简单示例,来展示useMemo如何使用的。

// 在Hooks中获取上一次指定的props
const usePrevProps = value => {
  const ref = React.useRef();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function App() {
  const [count, setCount] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const calcValue = React.useMemo(() => {
    return Array(100000).fill('').map(v => /*一些大量计算*/ v);
  }, [count]);
  const handleCount = () => setCount(count => count + 1);
  const handleTotal = () => setTotal(total + 1);
  const prevCalcValue = usePrevProps(calcValue);
  
  console.log('两次计算结果是否相等:', prevCalcValue === calcValue);
  return (
    <div>
      <div>Count is {count}</div>
       <div>Total is {total}</div>
      <br/>
      <div>
        <button onClick={handleCount}>Increment Count</button>
        <button onClick={handleTotal}>Increment Total</button>
      </div>
    </div>
  )
}

ReactDOM.render(<App />, document.body)

这次我们重点看这行,只有当count变量值改变的时候才会执行useMemo第一个入参的函数。

const calcValue = React.useMemo(() => {
    return Array(100000).fill('').map(v => /*一些大量计算*/ v);
  }, [count]);

通过useMemo的依赖我们就可以只在指定变量值更改时才执行计算,从而达到节约内存消耗。

总结

我们一起回顾了useCallbackuseMemo的基本使用方法,接着找到了影响性能的根本原因然后通过useCallback如何去解决性能问题。最后我们学习了如何使用useMemo去缓存了计算量密集的函数。我们还通过观察React Hooks源码观察了useCallbackuseMemo最根本的区别,这让我们在开发时可以做出正确的选择。

如果有错误请斧正

感谢阅读

recompose renameProp renameProps

renameProp renameProps 介绍

renameProp renameProps 可以改变指定props的名字,它们的功能类似,renameProp接收两个参数第一个参数为老的props第二个参数为命名后的propsrenameProps接收一个对象,其key值为老的propsvalue为命名后的props

renameProp Flow Type

renameProp(
  oldName: string,
  newName: string
): HigherOrderComponent

renameProps Flow Type

renameProps(
  nameMap: { [key: string]: string }
): HigherOrderComponent

renameProp renameProps 实例

const { compose, renameProp, renameProps } = Recompose;
const Foo = compose(
  renameProp('title', 'headline'),
  renameProps({ desc: 'description', age: 'old' }),
)(({ headline, description, old }) => ([
  <div>{headline}</div>,
  <div>{description}{old}</div>
]))

在线DEMO

codepen在线预览

docker入门之什么是container

进程(process)

容器(container)只是应用了附加配置的普通Linux进程。启动以下的Redis容器,我们可以用来了解幕后的情况。

docker run -d --name=db redis:alpine

Docker容器启动一个名为redis-server的进程。我们可以查看所有正在运行的进程,包括Docker启动的进程。

ps aux | grep redis-server

Docker可以通过以下方式帮助我们识别有关进程的信息,包括进程ID(PID)、父进程ID(PPID)。

docker top db

上面命令输出如下:

$ docker top db
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
999                 1109                1094                0                   15:06               ?                   00:00:00            redis-server

父进程ID是什么呢?使用ps aux | grep <ppid>找到父进程ID,有可能是容器(Containerd)。

这里我输入ps aux | grep 1094,得到如下结果:

root      1094  0.0  0.7   8924  7860 ?        Sl   15:06   0:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0 -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc -debug
root      1197  0.0  0.0  14220   928 pts/0    S+   15:10   0:00 grep --color=auto 1094

pstree命令将会列出所有子进程,使用pstree -c -p -A $(pgrep dockerd)查看Docker进程树

输出结果如下:

dockerd(677)-+-docker-containe(718)-+-docker-containe(1094)-+-redis-server(1109)-+-{redis-server}(1139)
             |                      |                       |                    |-{redis-server}(1140)
             |                      |                       |                    `-{redis-server}(1141)
             |                      |                       |-{docker-containe}(1095)
             |                      |                       |-{docker-containe}(1096)
             |                      |                       |-{docker-containe}(1097)
             |                      |                       |-{docker-containe}(1098)
             |                      |                       |-{docker-containe}(1100)
             |                      |                       `-{docker-containe}(1101)
             |                      |-{docker-containe}(721)
             |                      |-{docker-containe}(722)
             |                      |-{docker-containe}(723)
             |                      |-{docker-containe}(730)
             |                      |-{docker-containe}(738)
             |                      |-{docker-containe}(740)
             |                      |-{docker-containe}(755)
             |                      `-{docker-containe}(757)
             |-{dockerd}(704)
             |-{dockerd}(705)
             |-{dockerd}(706)
             |-{dockerd}(717)
             |-{dockerd}(719)
             |-{dockerd}(737)
             |-{dockerd}(739)
             |-{dockerd}(741)
             |-{dockerd}(748)
             `-{dockerd}(1093)

你亲眼所见了吧,从Linux角度来看,这些是标准进程,并且与我们系统上的其他进程具有相同的属性。

进程目录(Process Directory)

Linux只是一系列不可意思的文件和内容,这使得探索了解幕后发生的事情变得很有意思。

每个进程的配置在/proc目录中定义。如果知道进程ID,则可以配置标识(identify)目录。

下面的命令将列出/proc的所有内容,并存储Redis PID已供后面使用。

DBPID=$(pgrep redis-server)
echo Redis is $DBPID
Redis is 1109
$ ls /proc
1     1227  131  169  21   25   32   462  55   62   696  768  acpi       devices      interrupts  kmsg         misc          schedstat  sysrq-trigger  version_signature
10    124   132  17   217  26   33   465  56   63   7    775  buddyinfo  diskstats    iomem       kpagecgroup  modules       scsi       sysvipc        vmallocinfo
1094  125   133  171  22   262  34   494  57   64   708  8    bus        dma          ioports     kpagecount   mounts        self       thread-self    vmstat
11    126   134  172  225  267  35   5    58   65   715  85   cgroups    driver       irq         kpageflags   mtrr          slabinfo   timer_list     zoneinfo
1109  127   135  18   23   27   36   52   59   66   718  86   cmdline    execdomains  kallsyms    loadavg      net           softirqs   timer_stats
12    129   140  19   232  28   4    53   60   675  72   87   consoles   fb           kcore       locks        pagetypeinfo  stat       tty
1205  13    15   2    233  29   455  54   61   677  720  9    cpuinfo    filesystems  keys        mdstat       partitions    swaps      uptime
1220  130   16   20   24   3    457  543  610  68   725  905  crypto     fs           key-users   meminfo      sched_debug   sys        version

每个进程在不同文件中定义了自己的配置和安全设置 ls /proc/$DBPID 命令查看我们Redis Server的进程配置文件输出如下

attr       cgroup      comm             cwd      fd       io        map_files  mountinfo   net        oom_adj        pagemap      root       sessionid  stack  status   timers
autogroup  clear_refs  coredump_filter  environ  fdinfo   limits    maps       mounts      ns         oom_score      personality  sched      setgroups  stat   syscall  uid_map
auxv       cmdline     cpuset           exe      gid_map  loginuid  mem        mountstats  numa_maps  oom_score_adj  projid_map   schedstat  smaps      statm  task     wchan

举个例子,你可以查看和更新该进程的环境变量 cat /proc/$DBPID/environ, 输出如下

HOSTNAME=a125f186bfbeSHLVL=2REDIS_DOWNLOAD_SHA=61db74eabf6801f057fd24b590232f2f337d422280fd19486eca03be87d3a82bHOME=/home/redisPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binREDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.7.tar.gzREDIS_VERSION=5.0.7PWD=/data

docker exec -it db env 通过Docker容器查看环境变量,输出如下

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=a125f186bfbe
TERM=xterm
REDIS_VERSION=5.0.7
REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-5.0.7.tar.gz
REDIS_DOWNLOAD_SHA=61db74eabf6801f057fd24b590232f2f337d422280fd19486eca03be87d3a82b
HOME=/root

命名空间(Namespace)

容器的基本组成部分之一就是名称空间。名称空间的概念是为了限制哪些进程可以看到和访问系统的某些部分,例如其他网络接口或进程。

启动容器后,容器运行时(例如Docker)将创建新的名称空间以对进程进行沙箱处理。通过在它自己的PID 命名空间(Pid namespace)运行一个进程,它将看起来像是系统上唯一的进程。

可用的命名空间是:

  • Mount (mnt)

  • Process ID (pid)

  • Network (net)

  • Interprocess Communication (ipc)

  • UTS (hostnames)

  • User ID (user)

  • Control group (cgroup)

查看更多Linux Namespace信息

Unshare可以启动容器的进程

在不使用诸如Docker之类的运行时的情况下,进程仍可以在其自己的名称空间中运行。一种帮助工具是unshare。

unshare --help

使用unshare,可以启动进程并创建新的名称空间,例如Pid。通过从主机 unshare Pid命名空间,bash提示似乎是机器上唯一运行的进程。看起来好像bash是计算机上唯一运行的进程。

$ sudo unshare --fork --pid --mount-proc bash
$ ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 bash
    9 pts/0    00:00:00 ps
$ exit

当我们共享一个命名空间时会发生什么?

在幕后,命名空间是磁盘上的inode位置。这允许进程共享/重用相同的名称空间,从而允许它们查看和交互。

列出所有命名空间

$ ls -lha /proc/$DBPID/ns/
total 0
dr-x--x--x 2 999 packer 0 Jan 17 15:06 .
dr-xr-xr-x 9 999 packer 0 Jan 17 15:06 ..
lrwxrwxrwx 1 999 packer 0 Jan 17 15:42 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 ipc -> ipc:[4026532157]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 mnt -> mnt:[4026532155]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:06 net -> net:[4026532160]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 pid -> pid:[4026532158]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 user -> user:[4026531837]
lrwxrwxrwx 1 999 packer 0 Jan 17 15:18 uts -> uts:[4026532156]

另一个工具·NSEnter·用于将进程附加到现有的命名空间。用于调试目的

$ nsenter --help

$ nsenter --target $DBPID --mount --uts --ipc --net --pid ps aux
PID   USER     TIME  COMMAND
    1 redis     0:04 redis-server
   15 root      0:00 ps aux

使用Docker,可以使用以下语法共享这些命名空间container:<container-name>。例如,下面的命令会将nginx连接到数据库命名空间。

$ docker run -d --name=web --net=container:db nginx:alpine
$ WEBPID=$(pgrep nginx | tail -n1)
$ echo nginx is $WEBPID
$ cat /proc/$WEBPID/cgroup

网络共享后,它仍将作为命名空间列出

$ ls -lha /proc/$WEBPID/ns/
total 0
dr-x--x--x 2 systemd-network systemd-journal 0 Jan 17 15:49 .
dr-xr-xr-x 9 systemd-network systemd-journal 0 Jan 17 15:46 ..
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 ipc -> ipc:[4026532225]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 mnt -> mnt:[4026532223]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 net -> net:[4026532160]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 pid -> pid:[4026532226]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 user -> user:[4026531837]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 uts -> uts:[4026532224]

但是,两个进程的net命名空间指向相同的位置。

$ ls -lha /proc/$WEBPID/ns/ | grep net
dr-x--x--x 2 systemd-network systemd-journal 0 Jan 17 15:49 .
dr-xr-xr-x 9 systemd-network systemd-journal 0 Jan 17 15:46 ..
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 ipc -> ipc:[4026532225]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 mnt -> mnt:[4026532223]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 net -> net:[4026532160]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 pid -> pid:[4026532226]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 user -> user:[4026531837]
lrwxrwxrwx 1 systemd-network systemd-journal 0 Jan 17 15:49 uts -> uts:[4026532224]

$ ls -lha /proc/$DBPID/ns/ | grep net
lrwxrwxrwx 1 999 packer 0 Jan 17 15:06 net -> net:[4026532160]

Chroot

容器进程的重要部分是拥有独立于主机的不同文件的能力。这就是我们可以基于系统上运行的不同操作系统拥有不同的Docker映像的方式。

Chroot允许进程从不同的根目录启动到父操作系统。这允许不同的文件出现在根目录中。

Cgroups (Control Groups)

CGroup限制了进程可以消耗的资源量。这些cgroup是在/ proc目录内的特定文件中定义的值。

运行一下命令查看这些映射

$ cat /proc/$DBPID/cgroup
11:pids:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
10:perf_event:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
9:net_cls,net_prio:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
8:hugetlb:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
7:blkio:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
6:cpuset:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
5:freezer:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
4:cpu,cpuacct:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
3:devices:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
2:memory:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0
1:name=systemd:/docker/a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e

这些映射到磁盘上其他cgroup目录,位于:

$ ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  systemd

进程的CPU统计信息是什么?

CPU统计信息和使用情况也存储在文件中!

$ cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpuacct.stat
user 265
system 332

CPU份额限制也在此处定义

$ cat /sys/fs/cgroup/cpu,cpuacct/docker/$DBID/cpu.shares
1024

容器内存配置的所有Docker cgroup都存储在以下位置:

$ ls /sys/fs/cgroup/memory/docker/
085ac378ebb98d1f7d4b4cb233534bacfb057530a6e8f7e42b132faf23c5e804  memory.kmem.limit_in_bytes          memory.limit_in_bytes            memory.swappiness
a125f186bfbe2e89fccbb35829732e2ea7bcfdc216d456ed6ee2c186555ca2e0  memory.kmem.max_usage_in_bytes      memory.max_usage_in_bytes        memory.usage_in_bytes
cgroup.clone_children                                             memory.kmem.slabinfo                memory.move_charge_at_immigrate  memory.use_hierarchy
cgroup.event_control                                              memory.kmem.tcp.failcnt             memory.numa_stat                 notify_on_release
cgroup.procs                                                      memory.kmem.tcp.limit_in_bytes      memory.oom_control               tasks
memory.failcnt                                                    memory.kmem.tcp.max_usage_in_bytes  memory.pressure_level
memory.force_empty                                                memory.kmem.tcp.usage_in_bytes      memory.soft_limit_in_bytes
memory.kmem.failcnt                                               memory.kmem.usage_in_bytes          memory.stat

每个目录都根据Docker分配的容器ID进行分组。

$ DBID=$(docker ps --no-trunc | grep 'db' | awk '{print $1}')
$ WEBID=$(docker ps --no-trunc | grep 'nginx' | awk '{print $1}')
$ ls /sys/fs/cgroup/memory/docker/$DBID
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.soft_limit_in_bytes  notify_on_release
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.move_charge_at_immigrate  memory.stat                 tasks
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.numa_stat                 memory.swappiness
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.oom_control               memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.pressure_level            memory.use_hierarchy

如何配置cgroup?

Docker的特性之一是能够控制内存限制。这是通过cgroup设置完成的,默认情况下,容器对内存没有限制。我们可以通过docker stats命令查看。

$ docker stats db --no-stream
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
a125f186bfbe        db                  0.20%               8.277MiB / 992.1MiB   0.83%               1.3kB / 0B          0B / 0B             4

内存引用存储在一个名为memory.limit_in_bytes的文件中。通过写入文件,我们可以更改进程的限制。

echo 8000000 > /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes

如果你再去读它,你会看到已经被转换为了7999488

$ cat /sys/fs/cgroup/memory/docker/$DBID/memory.limit_in_bytes
7999488

再次检查Docker Stats时,该进程的内存限制现在为7.629M

$ docker stats db --no-stream
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS
a125f186bfbe        db                  0.19%               7.52MiB / 7.629MiB   98.57%              1.3kB / 0B          233kB / 0B          4

Seccomp / AppArmor

Linux的所有操作都是通过syscall完成的。内核有330个系统调用,它们执行诸如读取文件,关闭句柄和检查访问权限之类的操作

AppArmor是一个应用程序定义的配置文件,它描述了进程可以访问系统的哪些部分。

可以通过以下方式查看分配给流程的当前AppArmor配置文件

$ cat /proc/$DBPID/attr/current
docker-default (enforce)

Docker的默认AppArmor配置文件是docker-default (enforce)

在Docker 1.13之前,它将AppArmor配置文件存储在/etc/apparmor.d/docker-default(在Docker启动时被覆盖,因此用户无法修改它)。在v1.13之后,Docker现在在tmpfs中生成docker-default,使用apparmor_parser将其加载到内核中,然后删除该文件。

这是它的模板连接

Seccomp提供了限制可以进行哪些系统调用,阻止安装内核模块或更改文件权限等方面的功能。

默认的Docker允许调用

当分配给一个进程时,这意味着该进程将限于系统调用的一部分。如果它尝试调用被阻止的系统调用,则会收到错误“不允许操作”。

SecComp的状态也在文件中定义

$ cat /proc/$DBPID/status
$ cat /proc/$DBPID/status | grep Seccomp

该flag的含义是:0:禁用 1:严格 2:过滤

Capabilities

Capabilities是有关进程或用户有权执行的操作的分组。这些功能可能涵盖多个系统调用或操作,例如更改系统时间或主机名

状态文件还包含功能标志。一个进程可以丢弃尽可能多的功能以确保其安全性。

$ cat /proc/$DBPID/status | grep ^Cap

这些标志被存储为位掩码,可以用capsh解码

$ capsh --decode=00000000a80425fb

React.js绑定this的5种方法

this在javascript中已经相当灵活,把它放到React中给我们的选择就更加困惑了。下面一起来看看React this的5种绑定方法。

1.使用React.createClass

如果你使用的是React 15及以下的版本,你可能使用过React.createClass函数来创建一个组件。你在里面创建的所有函数的this将会自动绑定到组件上。

const App = React.createClass({
  handleClick() {
    console.log('this > ', this); // this 指向App组件本身
  },
  render() {
    return (
      <div onClick={this.handleClick}>test</div>
    );
  }
});

但是需要注意随着React 16版本的发布官方已经将改方法从React中移除

2.render方法中使用bind

如果你使用React.Component创建一个组件,在其中给某个组件/元素一个onClick属性,它现在并会自定绑定其this到当前组件,解决这个问题的方法是在事件函数后使用.bing(this)将this绑定到当前组件中。

class App extends React.Component {
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick.bind(this)}>test</div>
    )
  }
}

这种方法很简单,可能是大多数初学开发者在遇到问题后采用的一种方式。然后由于组件每次执行render将会重新分配函数这将会影响性能。特别是在你做了一些性能优化之后,它会破坏PureComponent性能。不推荐使用

3.render方法中使用箭头函数

这种方法使用了ES6的上下文绑定来让this指向当前组件,但是它同第2种存在着相同的性能问题,不推荐使用

class App extends React.Component {
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={e => this.handleClick(e)}>test</div>
    )
  }
}

下面的方法可以避免这些麻烦,同时也没有太多额外的麻烦。

4.构造函数中bind

为了避免在render中绑定this引发可能的性能问题,我们可以在constructor中预先进行绑定。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick}>test</div>
    )
  }
}

然后这种方法很明显在可读性和维护性上没有第2种和第3种有优势,但是第2种和第3种由于存在潜在的性能问题不推荐使用,那么现在推荐 ECMA stage-2 所提供的箭头函数绑定。

5.在定义阶段使用箭头函数绑定

要使用这个功能,需要在.babelrc种开启stage-2功能,绑定方法如下:

class App extends React.Component {
  constructor(props) {
    super(props);
  }
  handleClick = () => {
    console.log('this > ', this);
  }
  render() {
    return (
      <div onClick={this.handleClick}>test</div>
    )
  }
}

这种方法有很多优化:

  • 箭头函数会自动绑定到当前组件的作用域种,不会被call改变

  • 它避免了第2种和第3种的可能潜在的性能问题

  • 它避免了第4种绑定时大量重复的代码

总结:

如果你使用ES6和React 16以上的版本,最佳实践是使用第5种方法来绑定this

参考资料:

感谢阅读

iTerm2终端使用代理 && SSH(git) 代理

iTerm2终端配置代理

  1. 找到代理地址:127.0.0.1:50880

  2. 修改~/.bash_profile配置文件入下:

# proxy
export http_proxy=http://127.0.0.1:50880
export https_proxy=$http_proxy

如果不存在 ~/.bash_profile 则新建一个

  1. 执行 source 命令让配置文件立刻生效,source ~/.bash_profile

  2. 测试是否链接 curl -i https://google.com

ssh 代理

以上设置只针对http(s)的协议,如果clone的git仓库地址是git协议开头的,则无法使用代理速度还是非常非常的慢(6k~12k)。

配置代理后的效果:

image

这个时候需要配置ssh的代理(git协议是特殊的协议它走的是ssh的通道)

具体配置ssh的方法:

编辑 ~/.ssh/config,没有则创建一个

Host github.com
    User git
    ProxyCommand /usr/bin/nc -X 5 -x <socks_host>:<socks_port> %h %p

git特殊协议代理方法(我测试无效)

配置git配置(~/.gitconfig)

[core]
    gitproxy = /path/to/gitproxy for github.com

或使用命令更新git配置项:git config --global core.gitproxy "/path/to/gitproxy for github.com"

代理脚本:

#!/bin/sh
#
# Proxy wrapper for git protocol (9418).
#
# git config --global core.gitproxy "ido gitproxy"
#
exec /usr/bin/nc -X 5 -x <socks_host>:<socks_port> $1 $2

参考来源:https://yechengfu.com/blog/2015/01/16/use-git-behind-proxy/

基于对象的事件绑定

我们日常开发中基本上在使用javascript进行事件绑定都是这样子的

elem.addEventListener(type, listener[, useCapture]);

我们可以将绑定函数换成一个对象,就像下面这样

elem.addEventListener(type, this[, useCapture]);

其实在事件绑定中的监听函数也可以使用一个对象,事件触发之后会调用对象中的handleEvent函数进行事件的统一处理。

下面是一个使用事件绑定使用对象的例子

<div class="wrap">
  <button id="js_submit">提交</button>
  <textarea id="js_desc"></textarea>
</div>
var UiEvent = function() {}
UiEvent.prototype = {
  constructor: UiEvent,
  handleEvent: function(event) {
    var eventName = 'on' + event.type;
    if(this[eventName]) {
      this[eventName](event);
    }
  }
};

var Submit = function(){
  var name = 'js_submit';
  var elem = document.getElementById(name);
  this.bindEvent = function() {
    elem.addEventListener('click', this);
  }
  // 定义onclick处理函数
  this.onclick = function(event) {
    console.log(event);
  }
};
Submit.prototype = Object.create(UiEvent.prototype);

var submit = new Submit();
submit.bindEvent();

// Textarea
var Textarea = function(){
  var name = 'js_desc';
  var elem = document.getElementById(name);
  this.bindEvent = function() {
    elem.addEventListener('keyup', this);
  }
  // 定义onkeyup处理函数
  this.onkeyup = function(event) {
    console.log(event.key);
  }
};
Textarea.prototype = Object.create(UiEvent.prototype);

var textarea = new Textarea();
textarea.bindEvent();

UiEvent对象是一个专门同于处理事件的对象,其他的类可以继承此类,进行事件绑定

比如我们这里的Submit对象,它继承了UiEvent在事件绑定的时候使用elem.addEventListener('click', this);

当事件触发后会先调用UiEvent中的handleEvent,它会查找并执行Submit对象中的onclick方法。

recompose shouldUpdate

shouldUpdate 介绍

shouldUpdateReact shouldUpdate的高阶函数包装,用法同https://reactjs.org/docs/react-component.html#shouldcomponentupdate一样。

shouldUpdate Flow Type

shouldUpdate(
  test: (props: Object, nextProps: Object) => boolean
): HigherOrderComponent

shouldUpdate 例子

const Foo = compose(
  pure,
  shouldUpdate((props, nextProps) => props.title !== nextProps.title) ,
)(({ title }) => (
  <div>{console.log('render')}{title}</div>
))

在线DEMO

codepen在线预览

css继承介绍

在现实生活中继承是很常见的,除非有其他因素影响,一般情况下父母较高他的孩子也是比较高等等例子。我们在css中也可以看到类似的东西(inherit)。

CSS Inheritance - Inherit the Kingdom

如果你将一个容器元素color设置为绿色,除非有一些规则覆盖color这个颜色,容器内所有元素都会是绿色的。某些属性的值通过父元素传递到子元素的机制称为继承。

在本文中,你将会学习到关于继承的不同方面,以及它如何影响不同元素的外观。

css的继承有什么用?

css继承大大减少了创建网站所需要的时间和体力。想象一下你需要写多少css来给body和他的子元素设置color。这非常浪费时间,也很容易出错并且难以维护。同样,你可以想象如果强制你给每个子元素设置font-size,font-family,这肯定是一场噩梦。

来看一下这个演示

这里我给body元素定义了font-family,font-sizeline-height属性,这些值都被嵌套在body中的元素继承了。它的好处是给布局提供了一致性,而不需要在多个元素上重复设置同样的属性。

只有某些属性被继承

在现实生活中,不是所有的属性都是从父母你来继承过来的,css也是如此,并不是每个子元素的css属性都是默认继承的。事实上如果所有的css属性都被继承,它所带来的效果跟没有继承一样,因为你不得不写很多css来覆盖这种行为。

举个例子,如果border属性默认被继承的话,设置一个颜色有border会导致其所有子元素同样拥有border属性。同样的,如果子元素继承了父元素的background-image属性结果将非常混乱。

CSS Inheritance: An Introduction 原文

recompose defaultProps

defaultProps 介绍

defaultProps可以为组件增强一个默认属性,类似于withProps,如果组件未传递某个props则默认props会传递给组件。

defaultProps Flow Type

defaultProps(
  props: Object
): HigherOrderComponent

defaultProps 实例

const { compose, defaultProps } = Recompose;
const Foo = compose(
  defaultProps({
    title: "Hello,World"
  }),
)(({ title }) => (
  <div>{title}</div>
))

Foo组件没有传递title属性时,默认的title属性值'Hello,World'将会传递给组件的props

在线DEMO

codepen在线预览

recompose withStateHandlers

withStateHandlers 介绍

withStateHandlers就是withStatewithHandlers的结合并且对同一个state会进行多中操作时使用,它可以把statestate initialset state handler在一个地方进行创建,每个set state handler都是两个高阶函数包装的,第一个高阶函数接收(state, props),第二个高阶函数接收调用函数的参数。set state handler函数返回一个对象这个对象用于更新state

withStateHandlers Flow Type

withStateHandlers(
  initialState: Object | (props: Object) => any,
  stateUpdaters: {
    [key: string]: (state:Object, props:Object) => (...payload: any[]) => Object
  }
)

withStateHandlers 实例

const Foo = compose(
  withStateHandlers(
    ({ initialCount = 0 }) => ({
      counter: initialCount
    }),
    {
      handleIncrement: ({ counter }) => (value) => ({
        counter: counter + value,
      }),
      handleDecrement: ({ counter }) => (value) => ({
        counter: counter - value,
      }),
      handleReset: () => (initialCount = 0) => ({
        counter: initialCount,
      }),
    }
  ),
)(({ counter, handleIncrement, handleDecrement, handleReset }) => (
  <div>
    <p>Counter:{counter}</p>
    <button onClick={() => handleIncrement(1)}>增加</button>
    <button onClick={() => handleDecrement(1)}>减少</button>
    <button onClick={() => handleReset(0)}>重置</button>
  </div>
))

在线DEMO

codepen在线预览

如何检查变量是否在ruby中定义

在ruby中有defined?关键字帮助您检测一个变量是否定义

如果变量存在您将获得它的类型

apple = 1
p defined?(apple)
# => "local-variable"

如果变量没有定义您将获得nil

defined?(yoho) # => nil

这个 defined? 有点类似 javascript 的 typeof 运算符,但是如果你想知道对象的类型可以在对象上使用 class 方法

一些有意思的注意点:

  • defined? 是一个关键字,不是一个方法

  • defined? 是ruby中少出几个以 ? 结尾,但并不遵循常规的套路返回 truefalse

检查变量定义更好的方法

这个关键字很有用,但它存在一些问题。为什么?

因为其运算符优先级很低。

如果您像下面这样做:

defined? apple && apple.size

上面代码返回的结果是这个表达式的结果

因为 apple && apple.size 被解释为了 defined? 的参数

正确的做法应该是:

defined?(apple) && apple.size

ruby还有其他方法检查变量是否定义

local variables

local_variables.include?(:apple)

instance variables

instance_variable_defined?("@food")

但您可能不会使用这些

在99%的情况下,如果没有申明本地变量您会得到一个拼写错误

关于实例变量

未定义的实例变量总是为 nil,所以你需要检查它

可以尝试一下"安全导航操作符"(Ruby 2.3+) ,如果变量不是 nil 会调用这个方法

下面是一个例子:

if @user&.country == "Spain"
  # ...
end

上面的代码相当于:

if @user && @user.country == "Spain"
  # ...
end

"安全导航操作符"不像 defined? 那样普遍,但是它更容易预测更不容易出错

检查一个方法是否定义

您可以使用 defined? 检查方法是否定义。但它不是最佳实践

举个例子:

defined?(puts)
# "method"

因为 defined? 是一个关键字而不是方法,所以不能与对象一起使用

我是这个意思:

[].defined?(:size)
# undefined method `defined?' for []:Array

如何达到和类一起使用呢,像下面这样:

[].respond_to?(:size)
# true
[].respond_to?(:orange)
# false

检查一个类是否存在

举个例子:

defined?(Object)
# "constant"
defined?(A)
# nil

上面代码更好的方式是使用 const_defined? 方法。代码如下:

Object.const_defined?(:String)
# true
Object.const_defined?(:A)
# false

本章完啦。感谢阅读🙏

## Rails 路由学习

路由相关的类和模块在命名空间 ActionDispatch::Routing::Mapper中, 这里我给一个它的链接方便在学习时查看

match

匹配一个URL模式到一个或多个路由

基础使用

# 匹配格式为 :controller/:action. 请注意这样使用路由不能带参数
match 'account/setting', via: :get
Prefix Verb URI Pattern Controller#Action
account_setting GET /account/setting(.:format) account#setting

带参数的路由

# 带参数的路由必须指定 :to 参数, 他才能正确匹配到指定的 controller 和 action
match 'account/setting/:id',  to: "account#setting", via: :get
Prefix Verb URI Pattern Controller#Action
GET /account/setting/:id(.:format) account#setting

在路由匹配中使用通配符并且映射到params

match 'songs/*category/:title', to: 'songs#show', via: :get
Prefix Verb URI Pattern Controller#Action
GET /songs/*category/:title(.:format) songs#show

上面的通配符路由会将songs/rock/classic/stairway-to-heaven路径做匹配和映射

<ActionController::Parameters {"controller"=>"songs", "action"=>"show", "category"=>"rock/classic/abc", "title"=>"stairway-to-heaven"} permitted: false>

路由映射到 controller 和 action的几种写法

# 短写形式, match的第一个参数传递一个hash用来映射对应的路由和控制器
match 'photos/:id' => 'photos#show', via: :get
# 我们上面一直使用的正常模式(比较直观)
match 'photos/:id', to: 'photos#show', via: :get
# 冗余模式, 所有的参数都通过options来指定
match 'photos/:id', controller: 'photos', action: 'show', via: :get

除了以上match的几种使用方法外, 还可以指定兼容Rack方式的路由

match 'photos/:id', to: -> (hash) { [200, {}, ['Coming soon']] }, via: :get

match options

:controller

路由对应控制器名称

:action

路由对应控制器里的方法名称

:param

可以用于覆盖资源默认的:id参数, 一但覆盖你可以在控制的方法中使用params[:param]去访问

resources :photos, param: :title
Prefix Verb URI Pattern Controller#Action
photos GET /photos(.:format) photos#index
POST /photos(.:format) photos#create
new_photo GET /photos/new(.:format) photos#new
edit_photo GET /photos/:title/edit(.:format) photos#edit
photo GET /photos/:title(.:format) photos#show
PATCH /photos/:title(.:format) photos#update
PUT /photos/:title(.:format) photos#update
DELETE /photos/:title(.:format) photos#destroy

嗯我们看到之前默认:id已经被替换为了:title

为了更好配合上面路由的使用方法, 你还可以覆盖对应模型的to_param方法, 去构造符合路由的url

class Photo < ActiveRecord::Base
  def to_param
    name
  end
end

photo = Photo.find_by(name: 'ruby-china')
photo_path(photo)  # => "/photos/ruby-china"

:path

路由的前缀

:module

控制器的命名空间, 这里暂时还没懂. 稍后补上

:as

设置生成路由辅助函数的名称

match "account/setting", via: :get
match "account_as/setting_as", as: 'setting', via: :get
Prefix Verb URIPattern Controller#Action
account_setting GET /account/setting(.:format) account#setting
setting GET /account_as/setting_as(.:format) account_as#setting_as

通过上面的输出可以看出, 我们将account_as/setting_as路由的生成函数定义为了setting, 在代码中就可以这样使用setting_url setting_path

:via

定义路由所匹配的动作(verbs), 相关动作资料可以看这里

match 'path', to: 'c#a', via: :get             # get 
match 'path', to: 'c#a', via: [:get, :post]    # get/post
match 'path', to: 'c#a', via: :all             # all http verb

:on

可以为resource(s)定义的资源增加:member, :collection, :new路由. 请注意这个必须在resource(s)的do语句块中使用

resources :photos do
  match 'preview', to: 'photos#preview', on: :new, via: :get
  match 'preview', to: 'photos#preview', on: :member, via: :get
  match 'preview', to: 'photos#preview', on: :collection, via: :get
end

也等同于如下书写方法

resources :photos do
  member do
    match 'preview', to: 'photos#preview', via: :get
  end
end
Prefix Verb URIPattern Controller#Action
preview_new_photo GET /photos/new/preview(.:format) photos#preview
preview_photo GET /photos/:id/preview(.:format) photos#preview
preview_photos GET /photos/preview(.:format) photos#preview

:constraints

可以使用正则表达式或者一个可以响应matches?方法的对象来对参数进行约束

match 'account/setting/:id', constraints: {id: /[A-Z]\d{5}/},  to: 'account#setting', via: :get # 约束 /account/setting/A12345
match 'account/setting', constraints: { format: 'json' }, to: 'account#setting', via: :get # 这个不是太懂!
class Whitelist
  def matches?(request) request.remote_ip == '1.2.3.4' end
end
match 'account/setting', to: 'account#setting', constraints: Whitelist.new, via: :get
Prefix Verb URIPattern Controller#Action
GET /account/setting/:id(.:format) account#setting {:id=>/[A-Z]\d{5}/}
account_setting GET /account/setting(.:format) account#setting {:format=>"json"}
preview_photos GET /account/setting(.:format) account#setting

:defaults

设置默认params的值

match 'account/setting', to: 'account#setting', defaults: { id: 'hello' }, via: :get

params输出:<ActionController::Parameters {"id"=>"hello", "controller"=>"account", "action"=>"setting"} permitted: false>

总结:以上我们学会了match方法的基础使用和它的一些选项,在往后的使用中更能得心应手。关于get, post, delete, put其实也是调用的match方法

感谢阅读

recompose flattenProp

flattenProp 介绍

flattenProp可以将props中的对象扁平化到props中,类似于结构{...props.obj}

flattenProp Flow Type

flattenProp(
  propName: string
): HigherOrderComponent

flattenProp 实例

const { compose, flattenProp } = Recompose;
const Foo = compose(
  flattenProp('obj'),
)(({ title, desc }) => (
  <div>{title}, {desc}</div>
));

在线DEMO

codepen在线预览

recompose onlyUpdateForKeys

onlyUpdateForKeys 介绍

onlyUpdateForKeys接收一个数组字符串,只有当指定的props被修改时才更新。这个api不像shouldUpdate 不用自己写比较逻辑,它的内部会自动使用shouldUpdateshallowEqual进行包装。

onlyUpdateForKeys Flow Type

onlyUpdateForKeys(
  propKeys: Array<string>
): HigherOrderComponent

onlyUpdateForKeys 实例

const { compose, pure, onlyUpdateForKeys } = Recompose;

// 只有当 title props 被更改时才会更新组件(render)
const Foo = compose(
  pure,
  onlyUpdateForKeys(['title']) ,
)(({ title }) => (
  <div>{console.log('render')}{title}</div>
))

在线DEMO

codepen在线预览

前端开发知识整理

前端开发知识整理

整理包含前端的所有知识

JavaScript

基本切全面的指南参见MDN上的JavaScript主题

CSS

HTML

React Hooks 实用指南

React Hooks 实用指南

前言

React Conf 2018会议中,Dan Abramov 介绍了 React Hooks。官方的描述为

Hook是一项新功能提案,可让您在不编写类的情况下使用状态和其他React功能。 它们目前处于React v16.7.0-alpha中。计划将在 2019 Q1 推出到主版本中。

痛点

以下是React Hooks功能的动机,它解决了现有React中的一些问题

组件之间很难共享状态

React没有一种将可重用的行为附加到组件的方法(例如链接到store)。如果您使用过一段时间,您可能会使用render propsheight-order components组件解决这个问题。但是这些模式要求您再使用他们时重构组件,这会很麻烦。如果您使用React DevTools看一下您的程序,您会发现您的组件被各种组件所包裹这叫做包装地域,比如:providers、comsumers、higher-order components、render props等。这里有一个更深层的根本问题:React需要一个更好的方法来共享状态逻辑。

这就是Hooks,您可以从组件中提取有状态逻辑,以便可以独立测试和重用。Hooks允许您不更改组件层次结构的情况下重用有状态逻辑。这样就可以轻松在多组件之间或与社区共享Hooks

组件越来越复杂,变得难以理解

我们经常不得不维护一些组件,这些组件一开始很简单,随着时间的延伸组件发展成一堆无法管理的有状态逻辑和一些副作用。每个生命周期方法经常包含不相关的逻辑组合。举个例子,组件可能会在componentDidMountcomponentDidUpdate中拉取一些数据。还有componentDidMount方法可能还包含一些事件监听的不相关逻辑,并且再componentWillUnmount`中卸载监听。但是完全不相关的代码会合并到一个方法中。是很容易引起bug和不一致性。

在很多情况下,不能将这些组件拆分成更小的组件因为逻辑遍布许多地方。对它们进行测试也很困难。这正是很多人将React和状态管理库结合使用的原因。但是这更容易创建更多的抽象,要求您在许多不同的文件之间跳转,重用组件将变得更加困难。

为了解决这个问题,Hooks允许您根据相关的功能将他们拆分为一个更小的函数。而不是强制基于声明周期函数进行拆分。您还可以选择使用reducer管理组件的本地状态,使其更具可预测性。

类让人和机器都混淆

除了使代码重用和代码组织更加困难外,我们发现类(classes)可能成为学习React的一大障碍。您必须了解它在JavaScript中是如何工作的,这与它在大多数语言中的工作方式有很大不同。您必须明白如何正确的绑定事件处理和还没稳定的新语法,代码非常冗长。大家可能很容易就会明白属性(props)、状态(state)、从上往下的数据流(top-down data flow)但类(classes)就很难理解。React中的函数和类组件之间的区别以及何时使用每个组件导致即使在经验丰富的React开发人员之间也存在分歧。使用函数可以使用prepack更好的优化代码。但是使用类组件不能得到更好的优化。

为了解决这些问题,Hooks 允许您在没有类的情况下使用更多的React功能。

useState

useState可以让您的函数组件也具备类组件的state功能

使用语法如下:

const [state, setState] = useState(initialState);

useState返回一个数组,一个是state的值,第二个是更新state的函数

在真实的程序中我们可以这样使用:

function TestUseState() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <p>useState api</p>
      <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
    </div>
  )
}

使用 useState 需要注意一个事项,当你初始化是一个对象时。使用 setCount 时它不像类组件的 this.setState 会自动合并到 state 中。setCount 会使用当前的值覆盖之前的 state。如下所示

function TestUseStateObject() {
  const [state, setState] = React.useState({
    count: 0,
    greeting: "Hello, World!",
  });
  const handleAdd = () => {
    setState({
      count: state.count + 1
    })
  }
  console.log('state > ', state)
  return (
    <div>
      <p>useStateObject api</p>
      <p>Count: {state.count} <button onClick={handleAdd}>自增</button></p>
    </div>
  )
}

1543423038609

我们可以看到,当点击按钮时 state 被替换成了 {count: 1}。如果想要在 state 中使用一个对象需要在更新值的时候把之前的值解构出来,如下所示:

setState({
      ...state,
      count: state.count + 1
    })

在函数中使用多个 state

function TestMultipleUseState() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState('john');
  return (
    <div>
      <p>useState api</p>
      <p>Count: {count} - Name: {name}</p>
    </div>
  )
}

如需要在线测试请前往codepen useState

useEffect

默认情况下 useEffect 在完成渲染后运行,我们可以在这里获取DOM和处理其他副作用。但它还有两种不同的运行阶段稍候我会解释。

function TestUseEffect() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log(`组件被更新,Count: ${count}`);
  });
  
  return (
    <div>
      <p>useEffect api</p>
      <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
    </div>
  )
}

上面的 useEffect 在每次组件渲染后运行,每当我们点击自增按钮都会执行一次。

但是如果上面的代码在每次渲染后都执行,如果我们在 useEffect 从服务器拉取数据。造成的结果就是每次渲染后都会从服务器拉取数据。或者是只有某些 props 被更新后才想执行 useEffect。那么默认的 useEffect 就不是我们想要执行方式,这时 useEffect 提供了第二个参数。

useEffect(didUpdate, [])

useEffect第二个参数为一个数组。当我们提供第二个参数时,只有第二个参数被更改 useEffect 才会执行。利用第二个参数我们可以模拟出类组件的 componentDidMount 生命周期函数

function TestUseEffectListener() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    console.log('componentDidMount fetch Data...');
  }, []);
  
  return (
    <div>
      <p>TestUseEffectListener</p>
      <p>Count: {count} <button onClick={() => setCount(count + 1) }>自增</button></p>
    </div>
  )
}

上面的代码中 useEffect 只会执行一次,当您点击自增 useEffect 也不会再次执行。

useEffect 第一个参数的函数中我们可以返回一个函数用于执行清理功能,它会在ui组件被清理之前执行,结合上面所学的知识使用 useEffect 模拟 componentWillUnmount 生命周期函数

function TestUseEffectUnMount() {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    return () => {
      console.log('componentUnmount cleanup...');
    }
  }, []);
  
  return (
    <div>
      <p>TestUseEffectUnMount</p>
    </div>
  )
}

上面的代码中,当组件 TestUseEffectUnMount 将要销毁时会,会执行 console.log('componentUnmount cleanup...') 代码

如需要在线测试请前往codepen useEffect

useContext

useContext 可以让您在函数中使用 context,它有效的解决了以前 ProviderConsumer 需要额外包装组件的问题

使用语法如下:

const context = useContext(Context);

现在让我们来看看实际应用中这个 useContext 是如何使用的,代码如下:

function TestFuncContext() {
  const context = React.useContext(ThemeContext);

  return (
    <div style={context}>TestFuncContext</div>
  )
}

我们可以看到上面直接使用 React.useContext(ThemeContext) 就可以获得 context,而在之前的版本中需要像这样才能获取 <Consumer>({vlaue} => {})</Consumer> ,这极大的简化了代码的书写。

// 之前Consumer的访问方式
function TestNativeContext() {
  return (
    <ThemeContext.Consumer>
      {(value) => {
        return (
          <div style={value}>TestNativeContext</div>
        )
      }}
    </ThemeContext.Consumer>
  );
}

如需要在线测试请前往codepen useContext

useReducer

useReduceruseState 的代提方案。当你有一些更负责的数据时可以使用它。

使用语法如下:

const [state, dispatch] = useReducer(reducer, initialState)

第一个参数是一个 reduce 用来处理到来的 action,函数申明为:(state, action) => ()。第二个参数是一个初始化的state常量。

在返回值 [state, dispatch] 中,state 就是你的数据。dispatch 可以发起一个 action 到 reducer 中处理。

这个功能给我的感觉就是组件本地的redux,感觉还是不错。在设计一些复杂的数据结构是可以使用

现在让我们来看看实际应用中这个 useReducer 是如何使用的,代码如下:

function TestUseReducer() {
  const [state, setState] = React.useReducer((state, action) => {
    switch(action.type) {
      case 'update':
        return {name: action.payload}
      default:
        return state;
    }
  }, {name: ''});
  
  const handleNameChange = (e) => {
    setState({type: 'update', payload: e.target.value})
  }
  return (
    <div>
      <p>你好:{state.name}</p>
      <input onChange={handleNameChange} />
    </div>
  )
}

当改变 input 中的值时会同时更新 state 中的数据,然后显示在界面上

如需要在线测试请前往codepen useReducer

useCallback

useCallbackuseMemo 有些相似。它接收一个内联函数和一个数组,它返回的是一个记忆化版本的函数。

使用语法如下:

const memoizedValue = useMemo(() => computeExpensiveValue(a), [a])

useCallback 的第一个参数是一个函数用来执行一些操作和计算。第二个参数是一个数组,当这个数组里面的值改变时 useMemo 会重新执行更新这个匿名函数里面引用到 a 的值。这样描述可能有点不太好理解,下面看一个例子:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些计算
      return num;
    },
    [],
  );
  console.log('记忆 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div>
      <p>TestUseCallback</p>
    </div>
  )
}

_6d371a9a-47a9-4a5c-ac22-28a456b4d4d5

如果我们想监听 num 值的更新重新做一些操作和计算,我们可以给第二个参数放入 num 值,像下面这样:

function TestUseCallback({ num }) {
  const memoizedCallback = React.useCallback(
    () => {
      // 一些计算
      return num;
    },
    [num],
  );
  console.log('记忆 num > ', memoizedCallback())
  console.log('原始 num > ', num);
  return (
    <div>
      <p>TestUseCallback</p>
    </div>
  )
}

如需要在线测试请前往codepen useCallback

useRef

我觉得 useRef 的功能有点像类属性,或者说您想要在组件中记录一些值,并且这些值在稍后可以更改。

使用语法如下:

const refContainer = useRef(initialValue)

useRef 返回一个可变的对象,对象的 current 属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。

一个保存input元素,并使其获取焦点程序,代码如下:

function TestUseRef() {
  const inputEl = React.useRef(null);
  const onButtonClick = () => {
    // 点击按钮会设置input获取焦点
    inputEl.current.focus(); // 设置useRef返回对象的值
  };
  
  return (
    <div>
      <p>TestUseRef</p>
      <div>
        <input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>input聚焦</button>
      </div>
    </div>
  )
}

useRef 返回的对象您可以在其他地方设置比如: useEffect、useCallback等

如需要在线测试请前往codepen useRef

感谢阅读 🙏

最后做一个广告,我创建了一个前端周刊每周五发布最新的技术文章和开源项目欢迎订阅

recompose compose

compose 介绍

compose是一个组合辅助函数,它可以将多个高阶函数合并成一个函数。比如你有一些高阶函数它们需要这样组合执行a(b('input value'))如果函数较多这种写法就不太合适,这时我们可以使用compose将多个函数的执行组合成一个函数。例如:compose(a,c)('input value')。如果想了解更详细的信息请参考阮一峰老师的文章。

compose Flow Type

compose(...functions: Array<Function>): Function

compose接收一个或多个函数参数

compose 实例

const { compose } = Recompose;
function a(a) {
  return a + '-a func';
}
function b(b) {
  return b + '-b func';
}
var c = compose(a, b);
console.log(c('我是原始值')) // 我是原始值-b func-a func

在线DEMO

codepen在线预览

如何开启mongodb(3.0+)权限认证和远程链接

在开发fragmentwall的时候最开始是在本地进行开发,当我部署到服务器时我需要本地连接到服务器的mongodb数据,中间涉及了远程连接和权限验证设置。经过查阅文档已经完成,在此进行记录方便以后查阅和帮助其他需要的朋友。

创建用户

首先不要开启权限校验,你先创建一个超级用户。使用这个超级用户再去创建其他的用户和权限。

1.切换到admin数据库

use admin切换到admin数据,默认mongodb是没有这个数据的不过没有关系,当我们创建一个用户后它会自动创建。

2.创建一个超级用户

在admin数据库下使用下面的命令你可以给admin数据库创建一个超级用户

db.createUser({ user: "admin", pwd: "adminpassword", roles: [{ role: "userAdminAnyDatabase", db: "admin" }] })

执行完命令之后需要验证用户是否创建完毕使用命令 show users可以查看用户信息,如果有刚才创建的admin用户代表创建成功,然后使用exit退出控制台。

3.开启权限校验

mongodb数据库配置文件默认在/etc/mongod.conf中,使用你喜欢的编辑器打开它。sudo vim /etc/mongod.conf

然后将如下的安全设置打开(默认它是使用#注释起来的)

security:
	authorization: enabled

4.修改mongodb监听地址

默认的mongodb监听地址为bindIp: 127.0.0.1,它只允许本地连接,现在修改为bindIp: 0.0.0.0,这样你可以远程进行连接了。

net:
  port: 27017
  bindIp: 0.0.0.0

4.重启mongodb服务

使用命令sudo service mongod restart重启你的mongodb服务

5.创建授权用户

这个时候你可以为不同的数据库创建不同的用户和权限了,首选你需要连接上数据库,然后切换到admin数据库中

use admin切换到admin数据库中`

接着登录超级用户

db.auth("admin", "adminpassword")

跟着再切换到你要授权的其他数据库,比如我们需要将一个数据库testdatabase授权给monster这个用户,那么先要切换到testdatabase

use testdatabase

接着创建monster用户并且给予对应权限,命令如下

db.createUser({ user: "monster", pwd: "monsterpassword", roles: [{ role: "dbOwner", db: "testdatabase" }] })

执行完创建用户,所有的工作已经完成,下面来验证一下是否正确使用show users查看用户信息是否正确。然后使用这个用户登录再次验证。

db.auth("monster", "monsterpassword")
show collections

如果上面一切正确,那么monster就有了访问testdatabase这个数据的权限了

6.在程序中连接使用

那么mongodb连接的字符串为

mongodb://youruser:yourpassword@localhost/yourdatabase

官方关于安全的文档

希望能帮助到你 :)

recompose pure

pure 介绍

pure利用shouldUpdateshallowEqual来确定组件是否更新,它是一个对象浅比较

pure Flow Type

pure: HigherOrderComponent

pure 例子

const { compose, pure, shouldUpdate, lifecycle } = Recompose;

const Foo = compose(
  pure,
  shouldUpdate((props, nextProps) => props.title !== nextProps.title) ,
)(({ title }) => (
  <div>{console.log('render')}{title}</div>
))

在线DEMO

codepen在线预览

备注

可以结合recompose shouldUpdate来做自定义的props比较

rails使用前端React框架的相关配置

在rails中使用React.js框架相关的配置记录

目标:

  1. 创建基础项目
  2. 配置项目热加载

1. 创建基础项目

首先使用rails new命令创建一个rails的基础项目,完整的命令如下:

rails new railsUseReact --skip-turbolinks --skip-coffee --webpack=react

执行上述命令时稍微要等待一会,安装完毕之后即可继续。

2. 配置项目热加载

现在有了一个模板项目,我们需要创建一个路由用于测试。执行如下命令创建一个新的路由:

rails g controller Welcome index

接着我们需要开两个控制台, 分别在项目根目录执行如下命令

# 控制台1
rails s
# 控制台2
bin/webpack-dev-server

浏览器访问:http://localhost:3000/welcome/index 可以看到我们创建的页面

接下来我们先来看看模板项目为我们创建的一个javascript目录,它用来存放React.js组件相关的代码:

➜  railsUseReact git:(master) ✗ tree app/javascript
app/javascript
└── packs
    ├── application.js
    └── hello_react.jsx # 这里是一个React组件我们会使用它

然后修改app/views/welcome/index.html.erb使用javascript_pack_tag加载我们的hello_react组件,代码如下:

文件:app/views/welcome/index.html.erb

<h1>Welcome#index</h1>
<%= javascript_pack_tag 'hello_react' %>

刷新浏览器我们可以看到如下的输出:

Welcome#index
Hello React!

以上的修改可以查看提交记录

接着我们需要来对app/javascript/packs/hello_react.jsx组件进行一些改动,hello_react.jsx只保留入口组件。我们把真正的Hello组件单独存放。

创建如下的目录和文件:

➜  railsUseReact git:(master) ✗ tree app/javascript
app/javascript
├── components
│   └── Hello
│       ├── index.js
│       └── style.css
└── 其他文件

修改Hello组件为如下的内容:

文件:app/javascript/components/Hello/index.js

import React from 'react';
import PropTypes from 'prop-types';
import './style.css';

class Hello extends React.Component {
  render() {
    return (
      <div className="hello">Hello {this.props.name}!</div>
    )
  }
}

Hello.defaultProps = {
  name: 'David'
}

Hello.propTypes = {
  name: PropTypes.string
}

export default Hello;

文件: app/javascript/packs/hello_react.jsx

import React from 'react'
import ReactDOM from 'react-dom'
import Hello from '../components/Hello';

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Hello name="React" />,
    document.body.appendChild(document.createElement('div')),
  )
})

再次刷新浏览器可以看到如下内容:

Welcome#index
Hello React!

一切工作正常,接下来就配置热加载

启动webpack热加载

文件:config/webpacker.yml

  dev_server:
    hmr: true # 这里修改为true即可启动热加载

然后我们需要更新hello_react.jsx组件里面的内容使热加载是可以重新渲染组件

文件: app/javascript/packs/hello_react.jsx

import React from 'react'
import ReactDOM from 'react-dom'
import Hello from '../components/Hello';

const renderApp = (Component) => {
  ReactDOM.render(
    <Component name="React" />,
    document.body.appendChild(document.createElement('div')),
  )
}
document.addEventListener('DOMContentLoaded', () => {
  renderApp(Hello);
})

if (process.env.NODE_ENV !== 'production' && module.hot) {
  module.hot.accept('../components/Hello', () => {
    const NextHello = require('../components/Hello').default;
    renderApp(NextHello);
  });
}

接着安装react-hot-loader包来自动进行热替换,命令如下:

./bin/yarn add react-hot-loader

然后修改我们的Hello组件使用react-hot-loader包装

文件: app/javascript/components/Hello/index.js

import React from 'react';
import PropTypes from 'prop-types';
import { hot } from 'react-hot-loader';
import './style.css';

class Hello extends React.Component {
  render() {
    return (
      <div className="hello">Hello {this.props.name}!123</div>
    )
  }
}

Hello.defaultProps = {
  name: 'David'
}

Hello.propTypes = {
  name: PropTypes.string
}

export default hot(module)(Hello);

重启服务,然后再次访问一些正常。你可以尝试修改一下app/javascript/components/Hello/index.js组件的内容比如如下代码,可以通过Chrome 调试中的 Network查看页面并没有刷新然后组件的数据已经重新加载过了

以上的修改可以查看提交记录

整个配置完毕,感谢收看

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.