Coder Social home page Coder Social logo

blog's Introduction

blog

github上的博客系统 github上的博客会读取docs文件夹中的markdown和html文件

npm run build的时候是带包到dist文件夹下的,把dist文件夹下的内容拷贝到了docs下

blog's People

Contributors

yylgit avatar

Watchers

 avatar  avatar

blog's Issues

## JS 中的类型判断

JS 中的类型判断

js中的数据类型

基本数据类型

undefined、number、string、boolean

引用数据类型

null、Object、Number、String、Boolean、Function、Array、Date、RegExp、Error、Arguments

typeof

typeof操作符可能返回下面几种字符串

  • "undefined" 如果这个值未定义
  • "boolean" 如果这个值是布尔值
  • "string" 如果这个值是字符串
  • "number" 如果这个值是数值,注意其中NaN 返回的也是"number"
  • "function" 如果这个值是函数
  • "object" 如果这个值是对象或者是null

undefined boolean string number 都是基本的数据类型
function 和 object是引用类型,变量指向的是对象的地址,
对于引用类型的变量,typeof只可以区分出function,其他类型的统一识别成object。

boolean string 和number这三种基本的数据类型,都有对应的引用包装类型
Boolean String 和Number。

对于这些包装类型的变量,typeof统一识别成object

var a = new String('hello');
typeof a // object
var b = 'hello';
a === b // false
a ==b //true
a 实际变成了一个String类型的引用变量
所以a === b 是false,但是用== 比较的时候 b隐式调用了toString的方法 所以是true

image description
多说一句,其实我们在调用基本类型的方法的时候,都是隐式的转为包装对象以后才能调用。

instanceof

instanceof 应用于引用类型的判断,所以对于string number boolean 这三类基本类型没有什么意义。
instanceof 支持继承 因为所有的引用类型都继承自Object,所以所有引用变量都是Object的实例

var a = new String('hello');
a instanceof String  //true
a instanceof Object  //true
var b = 'hello';
b instanceof String  // false

我开始以为instanceof是通过判断a的__proto__ 上的constructor 属性来判断构造函数的类型,但是改变a.__proto__.constructor = Number 之后
a instanceof String 仍然为true

var a = new String('hello');
a.__proto__.constructor = Number;
a instanceof String  //true
a instanceof Number  //false

Object.prototype.toString.call()

这个是通过调用Object原型上的toString方法来判断变量的类型
这个方法不会区分是基本类型还是包装的引用类型,其实大多数情况下我们真不不需要区分。

var a = new String('hello')
var b = 'hello';
Object.prototype.toString.call(a)  //"[object String]"
Object.prototype.toString.call(b)  //"[object String]"

该方法还能够区分null和undefined

Object.prototype.toString.call(null)  //"[object Null]"
Object.prototype.toString.call(undefined)  //"[object Undefined]"

所以判断数据类型最靠谱的方法就是这个了。

underscore 中的实现

//代码中的toString 方法 就是Object.prototype.toString
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
  _.each(['Arguments','Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });
  
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
  };
  
虽然这个方法很好但是没有办法区分基本类型和引用类型,采用typeof可以判断:
    // 判断是否是引用类型
    _.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
    };

    //通过instanceof Object  应该也可以判断 是不是引用类型 并且不是null和undefined
  
  
    // 判断是否为数组
    _.isArray = nativeIsArray || function(obj) {
      return toString.call(obj) === '[object Array]';
    };
    //nativeIsArray 是ES5原生的Array.isArray
    
    //判断是否是NaN,利用NaN是唯一一个不等于自己的Number类型
    _.isNaN = function(obj) {
      return _.isNumber(obj) && obj !== +obj;
    };
    
    _.isUndefined = function(obj) {
        return obj === void 0;
    };
  
    _.isNull = function(obj) {
        return obj === null;
    };
    
     _.isFinite = function(obj) {
    return isFinite(obj) && !isNaN(parseFloat(obj));
    };
    
    //在 IE < 9 下对 arguments 调用 Object.prototype.toString.call,结果是 [object Object],所以利用他的callee属性来判断
    if (!_.isArguments(arguments)) {
        _.isArguments = function(obj) {
          return _.has(obj, 'callee');
        };
    }

React的生命周期与应用

目录

  • 1.react组件的两种创建写法
  • 2.组件的生命周期在不同状态下的执行顺序
  • 3.组件各生命周期的应用

1.react组件的两种创建写法

第一种ES5写法,React.createClass

React.createClass({
    getDefaultProps() {
        return {
            key1:value1
        }
    },
    getInitialState() {
        return {
            state1:state1
        }
    }
});

第二种是ES6写法,继承React.Component类

export default class Test1 extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            state1: state1
        }
    }
    static defaultProps = {
        data: 2,
    };
    static propTypes = {
        optionsData: React.PropTypes.array,
        onSelect: React.PropTypes.func,
        selectedOption: React.PropTypes.object,
        topStyle: React.PropTypes.any,
        placeholder: React.PropTypes.string
    }

}

getDefaultProps、getInitialState是在createClass时调用的方法,而在继承component类时,getDefaultProps方法对应给类添加静态属性 defaultProps ,getInitialState 对应在类的构造函数中设置 state属性

2.组件的生命周期在不同状态下的执行顺序

组件首次装载(first-Mount):

  • getDefaultProps() →
  • getInitialState() →
  • componentWillMount() →
  • render() →
  • componentDidMount()

卸载组件时(Unmount):componentWillUnmount()

当重新装载组件时(Re-Mount):

  • getInitialState()→
  • componentWillMount()→
  • render() →
  • componentDidMount(),
  • 但并不执行 getDefaultProps; defaultProps是放在组件类上的static属性

当再次渲染组件时(Re-Render),此时按顺序执行

  • componentWillReceiveProps(nextProps )(组件接收到新的props才会调用,只是改变state时不调用)→
  • shouldComponentUpdate(组件接收到新的props才会调用,只是改变state时不调用)→
  • componentWillUpdate→
  • render →
  • componentDidUpdate。

单独调用setState的重新渲染

  • componentWillUpdate→
  • render →
  • componentDidUpdate

1、在单页应用中,用react-router的history做页面跳转时,是将当前route的所有组件卸载,再跳转回来时是重新装载组件,而不是首次装载。

2、在使用react-native的Navigator时,每次push新页面的时候是首次加载,旧页面没有卸载,在pop新页面的时候,新页面会卸载 调用Unmount,旧页面是重新渲染

  • componentWillReceiveProps→
  • componentWillUpdate→
  • render →
  • componentDidUpdate。
    ,不是重新装载,也没有重新渲染的shouldComponentUpdate控制,所以pop回来肯定重新渲染。

3、组件在内存中装载过一次之后,组件的defaultProps就初始化了,之后装载就不会重新设置。

4、父组件的render都会引起子组件的重新渲染。

5、 不能在componentWillUpdate ,render和componentDidUpdate 中调用setState

3.组件各生命周期的应用

3.1 getDefaultProps方法 或者 defaultProps 对象

  • 只在组件类创建的时候调用一次,之后会被缓存起来。
  • defaultProps在组件实例之前创建,实例间共享。
  • 会和父组件设置的props进行合并。

3.2 getInitialState方法或constructor的state属性

项目中会把组件用到的state初始化在这里

constructor (props) {
    super(props);
    this.state = {
        poi: null,
        activeTabId: null,
        cartCount: Shopcart.size(),
        domSize:{
            headerHeight: 100,
            bannerHeight: 200,
            categoryTabHeight: 100,
        },
        hiddenBanner: false //是否隐藏banner
    };

}

3.3 componentWillMount()

组件初次render之前调用,如果在此方法中调用setState,render将感知到更新后的state,并且只执行这一次render,可以在此方法中fetch数据,不需要dom操作的数据获取。

3.4 render()

组件渲染的方法,是组件唯一的必须实现的方法,在该方法中,我们可以通过props和state渲染不同的组件。返回null或者false代表不渲染任何东西。

render () {
    return (
        <header className="header-wrapper">
            <div className="header header-normal">
                {this.renderLeftComponent()}
                <span>{this.props.title || '美团商超'}</span>
                {this.renderRightComponent()}
            </div>
        </header>
    );
}

3.5 componentDidMount()

组件装载后调用的方法,因为该方法调用的时候,组件已经装载,并且该方法不在render的循环当中,一般在该方法中做一些fetch数据或者改变state的方法。
还可以通过ReactDOM.findDOMNode(_this.refs.wrapper) 来获取DOM节点 进行操作。

componentDidMount() {
    this.mounted = true;
    if(this.props.poi){
        this.fetchCategoryTabs(this.props.poi.poiId);
    }
    if(!this.isCalculate) {
        this.calculateWidth(); 
    }
}

3.6 componentWillReceiveProps(nextProps)

在组件接收到新的 props 的时候调用。在初始化渲染的时候,该方法不会调用。可以在该方法中判断,当props变化时,是否再去重新fetch数据,setState。

componentWillReceiveProps (nextProps) {
    if(nextProps.poi &&(nextProps.poi != this.props.poi)) {
        this.fetchBannerList(nextProps.poi.poiId);
    }
}

3.7 shouldComponentUpdate(nextProps, nextState)

在接收到新的props或者state变化时,被调用,该方法在初始化渲染和forceUpdate的时候不会被调用。
默认返回true,如果返回false,则render不会执行。可以在这个方法中来阻止不必要的render,因为有时是因为父组件的render引起的子组件不必要的render。

shouldComponentUpdate(nextProps, nextState) {
    const isStateChanged = Object.keys(nextState).some(key=> {
        return nextState[key] !== this.state[key]
    });
    const isPropsChanged = Object.keys(nextProps).some(key=> {
        return nextProps[key] !== this.props[key]
    });
    return isStateChanged || isPropsChanged
}

3.8 componentWillUpdate(nextProps, nextState)

在接收到新的 props 或者 state 之前立刻调用。在初始化渲染的时候该方法不会被调用。使用该方法做一些更新之前的准备工作。你不能在刚方法中使用 this.setState()。如果需要更新 state 来响应某个 prop 的改变,请使用 componentWillReceiveProps。 项目中应用不多。

3.9 componentDidUpdate

在组件的更新已经同步到 DOM 中之后立刻被调用。该方法不会在初始化渲染的时候调用。使用该方法可以在组件更新之后操作 DOM 元素。

有些操作可能需要操作DOM元素,并且在第一次componentDidMount时还没有达到条件,所以需要在componentDidUpdate时再做操作,但是componentDidUpdate在render的循环函数中,
所以需要设置变量做控制。

下面例子中 this.isCalculate 就是判断是否计算过的变量。

componentDidMount() {
    this.mounted = true;
    if(this.props.poi){
        this.fetchCategoryTabs(this.props.poi.poiId);
    }
    if(!this.isCalculate) {
        this.calculateWidth(); 
    }
}
componentDidUpdate () {
    if(!this.isCalculate) {
        this.calculateWidth(); 
    }
}
calculateWidth () {
    if(this.isCalculate) {
        return;
    }
    let tablist = this.state.categoryTabs;
    if(tablist.length == 0) {
        return;
    }
    let tabsDOM = this.refs.tablist,
        childrensDOM = tabsDOM.childNodes,
        container = this.refs.tabcontainer,
        wrapper = this.refs.wrapper,
    // 横向滚动宽度
        scrollwidth = 5;
    for(let i=0; i<childrensDOM.length; i++){
        let childDOM = childrensDOM[i];
        scrollwidth += childDOM.clientWidth + parseInt(childDOM.style.marginRight);
    }
    scrollwidth = Math.max(tabsDOM.clientWidth,scrollwidth);
    this.setState({tabsWidth: scrollwidth + 'px'});

    this.props.setCategoryTabHeight(wrapper.offsetHeight);
    this.isCalculate = true;
}

3.10 componentWillUnmount

在组件从 DOM 中移除的时候立刻被调用。在该方法中执行任何必要的清理,比如无效的定时器,或者清除在 componentDidMount 中创建的 DOM 元素。
可以记录组件的mount状态,在 componentDidMount 中设置this.mounted = true 。 在componentWillUnmount 中设置 this.mounted = false。

React Native ScrollView中切换TextInput保持键盘展开

1、问题

问题场景:

由于手机屏幕高度不定,做表单页面时,外层通常加上ScrollView组件,使其能够适应屏幕进行滚动。业务需要里面放置多个TextInput组件。

问题描述:

出现的问题是,首次点击focus TextInput,键盘弹出,然后想要点击连续focus TextInput时,结果没有focus,而是键盘收起,需要再次点击TextInput 进行focus。
结果就是每次切换输入框都需要点击两次。

问题影响:

这个问题使得app的体验差到了极点,饱受用户吐槽。

2、解决方案

2.1 整体思路

保持键盘一直展开,然后点击的时候,能够判断点击的组件是否为TextInput,如果是则键盘不收起,否则键盘收起。

2.2 解决步骤

1) 在ScrollView中如何保持键盘一直展开

阅读ScrollView的源码可以发现,它有一个属性keyboardShouldPersistTaps,默认值为false,表示只要点击当前focus组件外的地方,都会引起键盘的收起。

这个是不能连续focus TextInput的原因,引起键盘收起后,还捕获了点击事件,使得focus失效。 所以我们应该把ScrollView的这个属性设置为 true,让键盘一直保持弹出状态。

它还有一个属性keyboardDismissMode,是枚举类型: 代表键盘在drag的时候是否收起

'none' 是默认值,代表键盘一直展开

'on-drag' 代表 ScrollView拖动的时候 键盘收起 在android下无效

'interactive' 翻译过来是互动的时候键盘收起

但是发现在android和ios下也没什么效果

尝试着用这个属性去收起键盘,但是效果不好,并且不兼容android。

最终我们对ScrollView的操作是加上属性 keyboardShouldPersistTaps = true

如何判断点击到的组件是TextInput ?

2)如何获取触摸事件的ID

阅读View的原文我们可以发现属性 onStartShouldSetResponderCapture,这个属性接收一个回调函数,函数原型是 function(evt): bool,在触摸事件开始(touchDown)的时候,RN 容器组件会回调此函数,询问组件是否要劫持事件响应者设置,自己接收事件处理,如果返回 true,表示需要劫持,如果返回false,表示不劫持。

传给回调函数的event里,包含一个触摸事件参数nativeEvent。nativeEvent 的详细内容如下:

  • identifier:触摸的 ID,一般对应手指,在多点触控的时候,用来区分是哪个手指的触摸事件;

  • locationX 和 locationY:触摸点相对组件的位置;

  • pageX 和 pageY:触摸点相对于屏幕的位置;

  • timestamp:当前触摸的事件的时间戳,可以用来进行滑动计算;

  • target:接收当前触摸事件的组件 ID;

  • changedTouches:evt数组,从上次回调上报的触摸事件,到这次上报之间的所有事件数组。因为用户触摸过程中,会产生大量事件,有时候可能没有及时上报,系统用这种方式批量上报;

  • touches:evt 数组,多点触摸的时候,包含当前所有触摸点的事件。
    这里我们用到的是target属性,它就代表点击的组件ID。

    3)如何根据组件ID知道组件是否为TextInput

    React-Native官网的0.28版文档中,View和TextInput组件中都有属性onLayout,这个属性接收一个回调函数,函数原型是 function(evt),在mount组件和layout的组件的时候触发该事件,传给回调函数的event里,参数nativeEvent,其中的target属性为该组件的ID。

    所以可以在TextInput组件layout时,将他们的ID存放在数组中,然后判断触发事件的组件ID是否在数组中,来确定该组件时候为TextInput。

    React-Native官网的0.28版文档中,View和TextInput组件中都有属性onLayout,这个属性接收一个回调函数,函数原型是 function(evt),在mount组件和layout的组件的时候触发该事件,传给回调函数的event里,参数nativeEvent,其中的target属性为该组件的ID。

    所以可以在TextInput组件layout时,将他们的ID存放在数组中,然后判断触发事件的组件ID是否在数组。

    0.28版本之前 TextInput组件没有onLayout属性,所以在0.28版本中TextInput没有onLayout属性,hack的方法是用View包裹TextInput,然后给View加onLayout属性,
    获取到的是View的组件ID,调试发现,View里面的TextInput的ID是外层View的ID+1,通过这种方法获取的TextInput的组件ID,在0.29以后的版本中,可以直接给TextInput加onLayout属性了。

4)如何控制键盘的收起

 react-native 中有dismissKeyboard模块,用于设置键盘的收起,直接调用该模块方法,即可收起键盘。

2.3 关键代码

const dismissKeyboard = require('dismissKeyboard');
const inputComponents = [];

    _onStartShouldSetResponderCapture (event) {
        let target = event.nativeEvent.target;
        if(!inputComponents.includes(target)) {
            dismissKeyboard();
        }
        return false;
    }

    _inputOnLayout(event){
        inputComponents.push(event.nativeEvent.target);
    }

<ScrollView ref="scrollView" keyboardShouldPersistTaps = {true} >
    <View onStartShouldSetResponderCapture={this._onStartShouldSetResponderCapture.bind(this)}>
          <TextInput onLayout={this._inputOnLayout.bind(this)} style={{flex: 1}}/>
    </View>
</ScrollView>

React 组件之间的通信方式

在项目开发的过程中,随着应用功能复杂度的增加和组件层次划分的需求,组件之间的通信越来越多,
我大致认为组件之间的通信分为3种:父-子组件通信、子-父组件通信和同级组件之间的通信。

  • 1.父-子组件通信
  • 2.子-父组件通信
  • 3.同级组件之间的通信

1.父-子组件通信

1.1通信的手段

这是最常见的通信方式,父组件只需要将子组件需要的props传给子组件,子组件直接通过this.props来使用。

1.2 通信内容

更多要提的是如何合理的设置子组件的props,要想将子组件设计成一个复用性强的通用组件,需要将能够复用的部分抽象出来,
抽象出来的props有两种形成,一种是简单的变量,另一种是抽象出来处理某种逻辑的函数。

以Header 组件为例
2016-01-22 4 30 59

抽象出来三个props,分别是中间的title,渲染组件左边的renderLeftComponent,渲染组件右边的renderRightComponent
Header的 部分实现

renderLeftComponent () {
    let leftDOM = {};

    if(this.props.renderLeftComponent) {
        return this.props.renderLeftComponent();
    }
    if (this.props.showBack) {
        let backFunc = this.props.onBack || this.goBack;
        leftDOM = (<a onClick={backFunc.bind(this)}><i className="icon left-icon icon-left-arrow"></i></a>);
    }
    return leftDOM;
}
renderRightComponent () {
    if(this.props.renderRightComponent) {
    return this.props.renderRightComponent();
}
}
render () {
    return (
        <header className="header-wrapper">
            <div className="header header-normal">
            {this.renderLeftComponent()}
            <span>{this.props.title || '维C果蔬'}</span>
            {this.renderRightComponent()}
            </div>
        </header>
    );
}

1.3 通信的动机

1.1中Header组件 props的通信动机 是子组件需要这样的属性来完成自己的展示。还有一种动机可以统称向子组件传递监听事件,
前一种是子组件的需求,后一种更多的是父组件的需求,例如Listview的onEndReached这种属性,触发源是在子组件中,当子组件
的事件被触发或者达到某种状态的时候,调用父组件从属性中传过来的方法。

2.子-父组件通信

2.1 通信的手段

父-子组件通信的手段是通过子组件的props是子组件用父组件的东西,子-父组件通信,是父组件用子组件的东西,应该将传递的内容直接写在子组件上,然后给子组件设置ref,父组件直接通过ref操作子组件的属性。

2.2 通信的内容

子组件的属性

2.3 通信的动机

父组件想要调用子组件的属性

3.同级组件之间的通信

同级组件之间的通信,是构建复杂界面的粘合剂,哎呦喂,我好会比喻啊
以维C果蔬的首页为例:
2016-01-22 5 22 16

通信1: Listview需要offsetHeight属性,Listview

Height的计算公式:window.innerHeight-Banner的Height-Menu的Height,

而Banner和Menu的Height都是需要在其Mount的时候才能计算。

通信2: ListView需要Menu的MenuId,才能够根据MenuId获取sku数据。

3.1通信的方式

同级组件之间的通信还是需要通过父组件作为中介,利用多次父-子组件通信,项目中将需要传递的数据放在了父组件的state中,变动时可以自动的同步传递。
将 bannerHeight,menuHeight,MenuId放在state中。
父组件代码示意:

this.state {
    bannerHeight: 0,
    menuHeight: 0,
    MenuId: 0
}
setBannerHeight(height) {
    this.setState({bannerHeight:height});
}
setMenuHeight(height) {
    this.setState({menuHeight:height});
}
onMenuClick(menuId) {
    this.setState({menuId:menuId});
}
<Banner setBannerHeight={this.setBannerHeight.bind(this)} />
<Menu setMenuHeight={the.setMenuHeight.bind(this)} onMenuClick={this.onMenuClick.bind(this)} />
<SkuList offsetHeight={this.state.bannerHeight + this.state.menuHeight} menuId={this.state.menuId} />

3.2通信的动机

当组件需要的props,不能直接从父组件中获取时,需要父组件作为中介,再与其他的组件进行通信获取。

Redux 入门

1.什么是Redux

管理Web应用全局状态的框架。

单页面应用,顾名思义,和传统项目的最明显区别就是项目只有一个页面,页面有一个根元素,我们写的每一个页面是一个大的组件,前端接管路由来渲染不同的页面组件。

随着应用的复杂,前端需要存储更多的state,包括缓存的全局数据,本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

如果是单纯的数据缓存 也没什么需要考虑的东西,放到内存就可以了, 重点是还要让state和view有绑定的关系。state的变化能够触发view的变化。

在我们的项目中,没有用redux之前,我们都是页面组件管理state,出现以下需求的时候,写起来就比较复杂

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

2.Redux的原则和设计**

2.1 三大原则

  • 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
  • State 是只读的:惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  • 使用纯函数来执行修改: 为了描述 action 如何改变 state tree ,你需要编写 reducers。只要是同样的输入,必定得到同样的输出。
    纯函数是函数式编程的概念,必须遵守以下一些约束。
    不得改写参数
    不能调用系统 I/O 的API
    不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

2.2 设计**:

(1)Web 应用是一个状态机,视图与状态是一一对应的。

(2)所有的状态,保存在一个对象里面。

3.Redux中的概念

3.1 Action

执行的动作,包括动作所需要的数据,改变store数据的唯一来源,一般是通过store.dispatch() 将 action 传到 store。

Action 本质上是 JavaScript 普通对象。

flux-standard-action FSA标准

  • type,必须,string或者Symbol
  • payload,可选,执行action所需要的数据,任何类型
  • error,可选,标识这个action是否有错误,true or false
  • meta,可选,任何类型,payload之外的其他数据。

###3.2 Reducer
根据action 做更新state的操作。

Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。

3.3 Store

Store就是保存全局state的容器,保存三个常用的api

  • getState,获取当前的state
  • subscribe,给store变化添加监听函数
  • dispatch,用于接受action的方法,触发reducer和监听函数

3.4 单项数据流

用户通过View,dispatch 相应的action,store调用reducer获取最新的state,并触发通过subscribe订阅的监听函数,监听函数中我们通过store的getState方法获取最新的state,更新view。
工作流程图
image description)

4.应用实例

simpleredux/index.js

import {createStore} from 'redux';
 
export function createAction(type, payload) {
    return {
        type,
        payload
    }
}
const initialState = {
    time: new Date().getTime()
}
 
function reducer(state = initialState, action) {
    switch (action.type) {
        case 'NOW_TIME':
            return {
                ...state,
                time: action.payload
            }
        default:
            return state;
    }
}
 
let store;
export function getStore() {
    if(store) return store;
    return store = createStore(reducer);
}

TestRedux.js

'use strict';
 
import React, { Component } from 'react';
 
import {
    StyleSheet,
    View,
    Text
} from 'react-native';
import MtButton from '@scfe/react-native-button';
import {getStore, createAction} from '../../simpleredux/index';
const store = getStore();
class TestRedux extends Component {
    constructor(props) {
        super(props);
        let state = store.getState();
        this.state = {
            time: state.time
        };
        store.subscribe(()=>{
            let state = store.getState();
            this.setState({
                time: state.time
            });
        });
    }
 
    _sendAction() {
        let action = createAction('NOW_TIME', new Date().getTime());
        store.dispatch(action);
    }
    render() {
        return (
            <View style={styles.container}>
                <Text>{this.state.time}
                </Text>
                <MtButton text="发出action" onPress={this._sendAction.bind(this)} />
            </View>
        );
    }
}
 
const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: 40
    }
});
 
export default TestRedux;

react native 开发小坑汇总

1.android下设置TextInput的height很小以后,大概是小于40,里面的文本显示不全

出现原因:
问题是因为android下的TextInput上下有最小的padding,height很小以后,paddingBottom + fontSize > height 所以显示不全。
解决办法:
给TextInput的paddingBottom 设置的小一点。让font能够在height里面垂直居中。

2.android下View设置的zIndex属性有显示bug

使用场景:
写的Select组件,想让option显示在上层,所以用的zIndex属性,但是在android下时出现显示的问题。

解决办法:
首先将Select组件放在布局的最后面,然后使用position: absolute 绝对定位,定位到需要的位置,因为Select组件放在的是布局的最后面,所以他也会在最上层显示

JS测试与接入CI指南

js代码自动化的测试有什么好处?

1、开发者在写测试脚本的时候,能够更好的理解代码的的功能,返回值等等。

2、能够实现准确直接的测试,并立即看到测试结果,进行调整。

3、面对复杂的项目,对代码的修改有可能会牵一发动全身,代码的改动可能会影响到其他部分的功能,自动化测试能帮我们整体检查一遍。

4、测试的结果能够当做一个代码质量的依据。

在segmentfault上搜索“探知js测试”,可以得到三篇系列文章对js测试进行讲解,第一篇的地址:https://segmentfault.com/a/1190000004428902

需要用到的知识包括:BDD的测试模式、Mocha测试框架、chai断言库,更倾向使用expect/should、istanbul 测试覆盖率工具,
这里有篇简单介绍 http://www.ruanyifeng.com/blog/2015/06/istanbul.html,需要学一下makefile的使用,supertest 测试api接口的工具

测试的项目:https://github.com/yylgit/test-demo
image description
项目接入travis平台
建立.travis.yml文件,文件内容

language: node_js
node_js:
- "5"
- "4"

travis 执行的是package中的scripts的test命令
接入后在travis平台上可以看到每当仓库有变动时重新执行测试,https://travis-ci.org/yylgit/test-demo

travsi每次都是在新的环境中进行测试
接入 coveralls平台,https://coveralls.io/github/yylgit/test-demo
node项目利用 node-coveralls +istanbul
image description

最终在github上的README.md中显示图标
image description

Redux系列源码解读

Redux 源码解读

1.redux-action createAction

redux-action的createAction方法就是给我们提供了灵活的创建符合FSA标准的action的方法。

exports.default = createAction;
 
/**
  返回创建action的函数
*/
function createAction(type, payloadCreator, metaCreator) {
  var finalPayloadCreator = typeof payloadCreator === 'function' ? payloadCreator : _identity2.default;
  /**
    返回的函数
  */
  var actionHandler = function actionHandler() {
    var hasError = (arguments.length <= 0 ? undefined : arguments[0]) instanceof Error;
  /**
    返回的action
  */
    var action = {
      type: type
    };
    //根据传入的参数,执行payloadCreator获取payload
    var payload = hasError ? arguments.length <= 0 ? undefined : arguments[0] : finalPayloadCreator.apply(undefined, arguments);
    if (!(payload === null || payload === undefined)) {
      action.payload = payload;
    }
 
    if (hasError) {
      // Handle FSA errors where the payload is an Error object. Set error.
      action.error = true;
    }
  //根据传入的参数,执行metaCreator获取payload
    if (typeof metaCreator === 'function') {
      action.meta = metaCreator.apply(undefined, arguments);
    }
    //可以看到  payloadCreator和metaCreator的参数都是用的传给actionHandler的参数
 
    return action;
  };
 
  actionHandler.toString = function () {
    return type.toString();
  };
 
  return actionHandler;
}

2.redux combineReducer

redux的combineReducer方法 用于将多个reducer,合并成一个大的reducer函数,传给store。

用于分解state树,每一个reducer对应state的一个key对应的子state。比如poi的reducer对应的就是state[poi]。这样在将state传递给props时利于分解。

reducer以对象的形式传入,finalReducers 存放最终的reducer,finalReducerKeys存放reducer的key
最终返回  combination函数 reducer类型的函数,接受state和action 返回state
 
state的形式是一个大对象下面每一个reducer对应一个子state。
 
触发一个action,会遍历所有的reducer,
将该reducer的旧state和action传入,然后根据返回的新的state对象是否改变,来决定
最终的返回的state是否改变。
这里需要注意:由于state都是引用类型,这里比较是值比较
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
 
所以如果我们想要改变全局的state,需要在reducer中返回新的对象,而不是原来的state对象,
如果返回原来的对象,即使对象里的值改变了,也不会引起全局state的改变。
 */
export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
 
    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
 
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)
 
  return function combination(state = {}, action) {
    /**
     校验语法错误,reducer返回的state不能是undefined
    */
    if (sanityError) {
      throw sanityError
    }
 
    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
      /**
        所以如果我们想要改变全局的state,需要在reducer中返回新的对象,而不是原来的state对象,
        如果返回原来的对象,即使对象里的值改变了,也不会引起全局state的改变。
      */
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

3 redux applyMiddleware

应用中间件的目的是包装dispatch,在action传递给dispatch执行之前,需要经过中间件的层层处理,进行一些业务上的处理,决定action的走向。

源码实现了这种将函数数组,通过reducerRight的方法,实现层层嵌套的执行,达到中间件的实现。

/**
  显示执行中间件,得到中间件的返回函数数组chain,然后利用compose方法,生成嵌套的执行chain
  方法的包装dispatch函数,
  中间件的形式是
  (getState, dispatch)=> next => action => {
     next(action);
  }
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []
 
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    /**
    store.dispatch 就是第一个next  是last ware的next
    (...args) => {
      return ware0(ware1(ware2(last(...args))))
    }
    dispatch = ware0(ware1(ware2(last(...args))))
    所以中间件中next传入后返回的函数就是我们需要的函数形式,
    例如dispatch 需要的函数形式是 传一个action
    */
    return {
      ...store,
      dispatch
    }
  }
}
 
/**
reduceRight是数组的从右至左执行,
初始的参数是最后一个函数接受dispatch,
的到的一个action=>{
    dispatch(action);
}
形式的函数,作为参数composed
f的形式是
next=>action=>{
}
最终形成的就是
(...args) => {
    return funcs0(funcs1(funcs2(last(...args))))
}
*/
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
 
  if (funcs.length === 1) {
    return funcs[0]
  }
 
  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

中间件执行过程模拟

中间件原理
*/
function func1 (next) {
    console.log('func1 return');
    return function (action) {
        console.log('func1start');
        next(action);
        console.log('func1end');
    }
     
}
function func2 (next) {
    console.log('func2 return');
    return function (action) {
        console.log('func2start');
        next(action);
        console.log('func2end');
    }
}
function func3 (next) {
    console.log('func3 return');
    return function (action) {
        console.log('func3start');
        next(action);
        console.log('func3end');
    }
}
 
function dispatch(action) {
    console.log(action);
}
 
function afterCompose (args) {
    return func1(func2(func3(args)));
}
 
var newdispatch = afterCompose(dispatch);
newdispatch('action');
/**
    执行顺序
    func3 return
    func2 return
    func1 return
    func1start
    func2start
    func3start
    action
    func3end
    func2end
    func1end
*/

4 redux createStore

应用场景参见中间件的应用代码与applyMiddleware源码,是redux提供创建store的方法。

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
 
export var ActionTypes = {
  INIT: '@@redux/INIT'
}
 
export default function createStore(reducer, preloadedState, enhancer) {
  var currentReducer = reducer
  var currentState = preloadedState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false
 
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
 
  function getState() {
    return currentState
  }
 
   /**
    订阅监听
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }
 
    var isSubscribed = true
 
    ensureCanMutateNextListeners()
    nextListeners.push(listener)
 
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
 
      isSubscribed = false
 
      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }
 
   /**
    执行reducer,获取state,执行listener
   */
  function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
 
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }
    return action
  }
 
   /**
    替换reducer
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }
 
 
  /**
    store创建的时候,获取初始的sate树
  */
  dispatch({ type: ActionTypes.INIT })
 
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer
  }
}

5. react-redux Provider

redux和react的结合,Provider作为根组件,将store的state放在context中供子组件使用。

import { Component, PropTypes, Children } from 'react'
import storeShape from '../utils/storeShape'
import warning from '../utils/warning'
 
 
export default class Provider extends Component {
  //把 store 放在context里面,给子元素用
  getChildContext() {
    return { store: this.store }
  }
 
  constructor(props, context) {
    super(props, context)
    this.store = props.store
  }
 
  render() {
    const { children } = this.props
    //渲染唯一的子元素
    return Children.only(children)
  }
}
 
Provider.propTypes = {
  store: storeShape.isRequired,
  children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
  store: storeShape.isRequired
}

6. react-redux connect

connect方法,将React的组件进行包装,包装的目的如下:

  • 能够将store中指定的state,传递给组件当props
  • 能够监听store中state的变化
  • 能够将action传递给view
const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapDispatchToProps = dispatch => ({ dispatch })
const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})
 
 
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
 
  //返回包装组件的函数
  return function wrapWithConnect(WrappedComponent) {
 
    class Connect extends Component {
      shouldComponentUpdate() {
        return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
      }
 
      constructor(props, context) {
        super(props, context)
        this.version = version
        this.store = props.store || context.store
 
       
        const storeState = this.store.getState()
        this.state = { storeState }
        this.clearCache()
      }
 
      isSubscribed() {
        return typeof this.unsubscribe === 'function'
      }
 
      trySubscribe() {
        if (shouldSubscribe && !this.unsubscribe) {
          //订阅store的state变化
          this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
          this.handleChange()
        }
      }
 
      tryUnsubscribe() {
        if (this.unsubscribe) {
          this.unsubscribe()
          this.unsubscribe = null
        }
      }
 
      componentDidMount() {
        //订阅store的state变化
        this.trySubscribe()
      }
 
      componentWillReceiveProps(nextProps) {
        if (!pure || !shallowEqual(nextProps, this.props)) {
          this.haveOwnPropsChanged = true
        }
      }
 
      componentWillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }
 
      //订阅变化
      handleChange() {
        if (!this.unsubscribe) {
          return
        }
 
        const storeState = this.store.getState()
        const prevStoreState = this.state.storeState
        if (pure && prevStoreState === storeState) {
          return
        }
 
        if (pure && !this.doStatePropsDependOnOwnProps) {
          const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this)
          if (!haveStatePropsChanged) {
            return
          }
          if (haveStatePropsChanged === errorObject) {
            this.statePropsPrecalculationError = errorObject.value
          }
          this.haveStatePropsBeenPrecalculated = true
        }
 
        this.hasStoreStateChanged = true
        //如果有变化 setState,触发render
        this.setState({ storeState })
      }
 
      render() {
        const {
          haveOwnPropsChanged,
          hasStoreStateChanged,
          haveStatePropsBeenPrecalculated,
          statePropsPrecalculationError,
          renderedElement
        } = this
 
        
        //最终渲染组件,将合并属性传递给WrappedComponent
        if (withRef) {
          this.renderedElement = createElement(WrappedComponent, {
            ...this.mergedProps,
            ref: 'wrappedInstance'
          })
        } else {
          this.renderedElement = createElement(WrappedComponent,
            this.mergedProps
          )
        }
 
        return this.renderedElement
      }
    }
 
    Connect.displayName = connectDisplayName
    Connect.WrappedComponent = WrappedComponent
    Connect.contextTypes = {
      store: storeShape
    }
    Connect.propTypes = {
      store: storeShape
    }
 
    //把WrappedComponent的非静态react属性 复制到Connect,最终返回Connect
    return hoistStatics(Connect, WrappedComponent)
  }
}

7. redux bindActionCreators

使用实例参见 react-redux connect 的使用实例

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}
 
 
  将actionCreators绑定上dispatch,key还是actionCreators的key,但是多做了一层dispatch
 */
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
 
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }
 
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

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.