CuriousY A world with wonder

打造可与后端无缝衔接的前端开发环境

| Comment

在开发前端页面时,往往需要和后端的服务器打交道,而现在前端代码和后端通常是分开来由不同的人来开发的,并且后端往往也不是Node.js实现的。那么这些和后端接触的部分前端代码就显得非常难受。作为初学者,我们可能会去在代码里面hard code一些数据来mock这些请求,但显然这样做比较蠢,会留下一些没有必要的代码。另外一个思路就是直接去mock后端的服务器,而不仅仅是某个请求。本文就将探讨这个思路下的可与后端无缝衔接的React开发环境的搭建。

从create-react-app入手###

首先,以下主要讲的是开发React的应用,如果是用其他框架的话,思路也可以借鉴。

这里默认React应用是通过create-react-app来创建的,使用这个工具的好处在于它帮我们集成了许多我们需要的lib,功能还是比较齐全的,使用时建议看一看官方的guide

create-react-app本身集成了一个HTTP server(假设就叫dev server),用于host我的所有静态文件,并且我代码中的修改保存后是可以实时反映到页面上的(即支持热加载),这大大提高了开发的效率。因为这里需要去模拟整个后端服务器,所以我们会另启一个HTTP server作为mock的服务器(假设叫API server)。为了避开跨域请求的问题,这里将dev server设置成API server的代理服务器。当我在我前端的代码中发送请求时,通过代理就可以得到API server中的数据,并且请求的域名仍然是host我的js代码的dev server。整个流程如下图所示:

proxy using create-react-app

create-react-app设置代理非常简单,只需要在package.json中添加如下的设置即可以让所有在dev server上无法找到的资源导向API server:

"proxy": "http://localhost:3001"
Read more

Cannot proxy using create-react-app

| Comment

在研究打造可与后端无缝衔接的前端开发环境时,发现在package.json中设置了proxy字段,却仍然无法再浏览器中访问被代理的那部分资源。并且尝试了使用不同的http server作为被代理服务器,都没有用。

那么问题可能就出在我的前端代码中,或者就是create-react-app的bug。因为我的代码中用到了react-router这个库,一开始我以为是这个lib有问题,但google了下,并没有人提出类似的内容,并且我使用了另外的很简单的前端代码也同样无法得到被代理内容,情况被排除。至于create-react-app的bug的情况,在它的github的issue里面也没有发现类似的情况,并且对比了我本地安装的lib和最新的code,也没有变化,这种可能性也被排除。

这下我就有点懵逼了,就一个简单的代理还能出多复杂的幺蛾子?

Read more

React组件是如何渲染的

| Comment

问题如题,虽然之前也有过这个疑惑,但一直就放在那。直到最近看Redux相关的文章,看到这么一段代码:

// CONTAINER
export const view = connect(
    state => ({count: state})
)(
    ({name, count, dispatch}) => <div>
        <p>{name}:
            {count}
            <button onClick={e => dispatch(decrement())}>-</button>
            <button onClick={e => dispatch(increment())}>+</button>
        </p>
    </div>
)

其中connect函数最后面的输入是一个React组件对象,在这里它其实就是一个很简单的函数返回了一个DOM对象。React组件到底都支持哪些形式呢?

React源码剖析1

要看React组件支持哪些形式,其实主要是看在进行组件渲染时(将virtual DOM渲染成真实的DOM),React是怎么做的。以下内容会比较啰嗦,TL;DR,可以直接看结论

我们在渲染React组件时首先会调用ReactDOM.render,像下面这样将我们的组件作为参数输入进去:

import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App/>,
    document.getElementById('root')
);
  1. reactreact-dom均以“15.4.2”版本为准。 

Read more

Reading <Docker -- 从入门到实践> - 2

| Comment

高级网络配置

当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。

同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

Docker 网络

可以理解为在宿主机器上建立了一个容器之间互通的“局域网”。


其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。

  • -b BRIDGE or --bridge=BRIDGE –指定容器挂载的网桥
  • --bip=CIDR –定制 docker0 的掩码
  • -H SOCKET... or --host=SOCKET... –Docker 服务端接收命令的通道
  • --icc=true|false –是否支持容器之间进行通信
  • --ip-forward=true|false –请看下文容器之间的通信
  • --iptables=true|false –是否允许 Docker 添加 iptables 规则
  • --mtu=BYTES –容器网络中的 MTU

下面2个命令选项既可以在启动服务时指定,也可以 Docker 容器启动(docker run)时候指定。在 Docker 服务启动的时候指定则会成为默认值,后面执行 docker run 时可以覆盖设置的默认值。

  • --dns=IP_ADDRESS... –使用指定的DNS服务器
  • --dns-search=DOMAIN... –指定DNS搜索域

最后这些选项只有在 docker run 执行时使用,因为它是针对容器的特性内容。

  • -h HOSTNAME or --hostname=HOSTNAME –配置容器主机名
  • --link=CONTAINER_NAME:ALIAS –添加到另一个容器的连接
  • --net=bridge|none|container:NAME_or_ID|host –配置容器的桥接模式
  • -p SPEC or --publish=SPEC –映射容器端口到宿主主机
  • -P or --publish-all=true|false –映射容器所有端口到宿主主机

Docker服务是指后台一直运行的Docker server。


容器之间相互访问,需要两方面的支持。

  • 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。
  • 本地系统的防火墙软件 – iptables 是否允许通过。

这里的默认情况是指容器的--net设置为bridge(默认设置)。


默认情况下,不同容器之间是允许网络互通的。如果为了安全考虑,可以在 /etc/default/docker 文件中配置 DOCKER_OPTS=--icc=false 来禁止它。

这个配置的是Docker服务,即配置了过后所有启动的容器网络都是不通的。


在通过 --icc=false 关闭网络访问后,可以通过 --link=CONTAINER_NAME:ALIAS 选项来访问容器的开放端口。

注意:--link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必须是 Docker 分配的名字,或使用 --name 参数指定的名字。主机名则不会被识别。

这个配置就是针对某个要启动的容器而言的,它的实现其实就是在各自容器的iptables里面把彼此添加到whitelist里面。

Read more

Redux进阶之组件的封装

| Comment

可能是后端弄得多了,上手了一个新东西后,就开始考虑怎么样去封装、模块化了。考虑到使用了Redux之后的组件的封装,我首先想到的是将Redux绑定好的组件和reducer函数打包起来,使用时可以根据组件的某个属性(比如id属性)来唯一确定它在state tree中的位置,从而封装和隔离。但实践起来却碰到下面这样一个问题。

reducer函数传递的state是一个相对路径下的state,而mapStateToProps函数传递得到的却是Redux中存储的整个state。这样就会产生一个问题:为了获取state特定路径下的某个状态,必须要hard code一个路径,这与(组件)代码复用是相矛盾的。那么如何在mapStateToProps中也同样得到一个相对路径下的state呢?

举个例子来说,我有下面这样一个结构的state:

let state = {
  out: "QWE",
  inside: {
    firstname: "abc",
    lastname: "def",
  }
}

上面的每一个状态都对应了不同id(比如分别为’inside-firstname’, ‘inside-lastname’和’out’)的相同组件:

const mapStateToProps = (state, ownProps) => {
    return {
        value: getRelativeState(state)[ownProps.id].value,
    }
};

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        handleValueChange: (event) => {
            dispatch(createAction(ownProps.id, event.target.value));
        },
    }
};

const Input = connect(mapStateToProps, mapDispatchToProps)(BaseInput);

问题是上面的getRelativeState该如何实现?或者是否有更好地实现方式?

react-redux源码剖析

上面的问题放一放,先看一看为什么Redux在这两个函数中传递的state会不一样。首先,reducer函数是通过combineReducers函数来结合在一起的,combineReducer函数在内部帮我们做了一个映射。看看combineReducer的源码就明白了:

export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  var finalReducerKeys = Object.keys(finalReducers)
  // 省略若干行state和类型检查的代码
  return function combination(state = {}, action) {
    // 省略若干行state和类型检查的代码
    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

以上,很多东西就比较清楚了:

  • combineReducers返回的仍然是一个reducer函数(输入为state和action);
  • 对于combineReducers输入的每一个reducer函数都会在action出现时被依次调用,并用来更新总的state;
  • reducer函数返回的状态如果是undefined的话是会报错的;
  • 如果一个action下导致的所有的子reducer返回的状态都没变,那么总的state对象是不变的(包括对象的内存地址),否则得到的是一个全新创建的对象(这里的hasChanged变量的作用)。
Read more
| Page 13 of 25 |