React-高级教程完整版

React 高级教程完整版

这标题可能有点不太贴切或符合内容,从官方上来区分这部分内容确实属于高级部分,只是由于个人原因,在后面的一些章节并没有记录在列。

也为了承接上一篇,因此勉强将标题定位:“React 高级教程完整版”

纯属针对个人学习记录成果,无他~~~

属性类型检测(Typechecking With PropTypes)

React 内置了一系列的类型那个检测功能,通过 Comp.propTypes 对象指定,比如:

Comp.propTypes = {
    name: React.PropTypes.string
};

经过上面的指定之后,如果传入的 name 为非字符串的,会报错。

看个小示例:

// props-type-checking.html

class TypeCheck extends React.Component {
    constructor(props) {
        super(props);

    }

    render() {
        return (
            <div>incoming prop: {this.props.sayHello}div>
        );
    }
}

// 类型检测在类外面指定
TypeCheck.propTypes = {
    sayHello: React.PropTypes.string
};

ReactDOM.render(
    11111} />,
    document.getElementById('root')
);

报错内容:

Warning: Failed prop type: Invalid prop sayHello of type number supplied to TypeCheck, expected string in TypeCheck

检测类型:

类型 属性
数组 React.PropTypes.array
Boolean值 React.PropTypes.bool
函数 React.PropTypes.func
数字 React.PropTypes.number
对象 React.PropTypes.object
字符串 React.PropTypes.string
符号 React.PropTypes.symbol
DOM元素 React.PropTypes.node
React元素 React.PropTypes.element
某一个类的实例 React.PropTypes.instanceOf(Message)
属性值限定在某一范围 React.PropTypes.oneOf([‘News’, ‘Photos’]),
多种类型的一种 React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number,
    React.PropTypes.instanceOf(Message)
])
数组且限定数组成员类型 React.PropTypes.arrayOf(React.PropTypes.number)
对象且限定对象成员值类型 React.PropTypes.objectOf(React.PropTypes.number)
特定形状的对象 React.PropTypes.shape({
 color: React.PropTypes.string,
 fontSize: React.PropTypes.number
})

并且可以在上面的属性检测后面加上 isRequired 来指定该属性是否必须,不如指定了而没有传递该属性,则会报下面错误

Warning: Failed prop type: The prop sayHello is marked as required in TypeCheck, but its value is undefined in TypeCheck

如果直接使用 React.PropTypes.isRequired 也会报错:

Warning: Failed prop type: TypeCheck: prop type sayHello is invalid; it must be a function, usually from React.PropTypes. in TypeCheck

上面意思大概就是:sayHello 类型无效,必须是个函数,通常来自 React.PropTypes

PropTypes: ReactPropTypes,

会发现 ReactPropTypes 其实是个包含类型检测函数的对象

var ReactPropTypes = {
  array: createPrimitiveTypeChecker('array'),
  bool: createPrimitiveTypeChecker('boolean'),
  func: createPrimitiveTypeChecker('function'),
  number: createPrimitiveTypeChecker('number'),
  object: createPrimitiveTypeChecker('object'),
  string: createPrimitiveTypeChecker('string'),
  symbol: createPrimitiveTypeChecker('symbol'),

  any: createAnyTypeChecker(),
  arrayOf: createArrayOfTypeChecker,
  element: createElementTypeChecker(),
  instanceOf: createInstanceTypeChecker,
  node: createNodeChecker(),
  objectOf: createObjectOfTypeChecker,
  oneOf: createEnumTypeChecker,
  oneOfType: createUnionTypeChecker,
  shape: createShapeTypeChecker
};

从上面可知 ReactPropTypes 对象下是没有 isRequired 这个属性的,再往下看,会发现其实 isRequired 是挂接在上面的执行结果之后的,也就是说就算指定了 isRequired 也会线执行上面的类型检查,然后再去根据 isRequired 值去进一步检测是类型不对还是压根没有传这个属性;


function createChainableTypeChecker(validate) {

    // 省略 ......

  function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
    if (props[propName] == null) {
      var locationName = ReactPropTypeLocationNames[location];
      if (isRequired) {
        if (props[propName] === null) {
          return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
        }
        return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
      }
      return null;
    } else {
      return validate(props, propName, componentName, location, propFullName);
    }
  }

  1. 先传入 false 执行基本的类型检查 validate,得到检查后的结果,并且结果有两种
      1. 属性存在,但是类型错误,这时候会直接返回错误对象
      2. 属性不存在,则会返回错误信息,并且继续执行下面一句
  var chainedCheckType = checkType.bind(null, false);


  2. 这一步执行 `isRequired` 检测,并且是经过上面一步类型检测之后,原因在于如果不添加 `isRequired` 在类型错误的时候由上面检测返回错误结果。如果有那么下面的检测结果中的错误会替代上面一步中 validate 返回的错误检测结果

  chainedCheckType.isRequired = checkType.bind(null, true);

  return chainedCheckType;

}

最终在对象 ReactPropTypes 中的成员值就是上面函数返回的 chainedCheckType 对象,里面包含了错误信息对象和isRequired(这个也是个 PropTypeError 对象,或者 null);

比如上面如果这样指定

sayHello: React.PropTypes.string.isRequired

其实最后的结果就是 chainedCheckType.isRequired 这个的结果值,我们的错误信息也就是这里面来的。

上面是个人对源码中 isRequired 的处理逻辑的大概理解。


下面是官方提供的两个自定义属性检测的示例

简单属性检测:

// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.
customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
}

定义也比较简单,需要注意的是上面的注释,让我们报错的时候不要使用 console.warn 或者 throw 方式,因为在 oneOfType 里面不能正常工作。

数组(或对象)的检测自定义:

 // You can also supply a custom validator to `arrayOf` and `objectOf`.
// It should return an Error object if the validation fails. The validator
// will be called for each key in the array or object. The first two
// arguments of the validator are the array or object itself, and the
// current item's key.
customArrayProp: React.PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
    })
};

下面来尝试下自定义方式检测属性

  • 属性值中必须包含 Hello 字符串

    把上面的示例改一下

    // props-type-checking.html
    
    TypeCheck.propTypes = {
        sayHello: function (props, propName, componentName) {
            if ( !/Hello/.test(props[propName]) ) {
                return new Error(
                    'Invalid prop `' + propName + '` supplied to' +
                    ' `' + componentName + '`. Validation failed.'
                );
            }
        }
    };

    控制台输出错误:(整好跟我们自定义的结果相同)

    Warning: Failed prop type: Invalid prop sayHello supplied to TypeCheck. Validation failed. in TypeCheck

    然后把属性值修改成如下:(把 hello 改成首字母大写 Hello

    ReactDOM.render(
        'Hello world!' />,
        document.getElementById('root')
    );

    控制台没有错误输出,说明我们自定义的类型检测函数成功。

  • defaultProps 默认属性值对象

除了属性值检测以外,我们还可以通过 defaultProps 来指定属性的默认值,使用方法;

Comp.defaultProps = {
    name: 'lizc'
};

指定之后,如果没有传入该属性则会启用该默认属性值。

RefsDOM(Refs and the DOM)

在典型的 React 数据流中, props 是唯一一种能让父组件与子组件进行沟通的方式,比如去修改子组件,刷新重绘子组件都需要用到 props 属性对象;但是,有些情况必须要在组件外部或者组件渲染完成之后去引用它,那这个时候 props 就显得无能为力了,因为这个属性对象只能在组件内部使用。

因此就有 refs 这个东西,其实更具体点,这个 ref 对象可以视为未渲染或者渲染之后的组件对象

如果没有渲染那么代表的是 react 组件,

如果组件被渲染之后 ref 指向了 DOM 书中的该组件对象;

  1. ref 使用时机

    如果能用属性来控制的,尽量避免使用 ref

    • 管理组件焦点(focus),文本选择(text selection)或者媒体播放(media playback)
    • 触发必须执行的动画
    • 集成第三方 DOM
  2. DOM 元素添加 ref(引用)

在定义组件的时候,可以给组件添加一个 ref 属性,这个属性值是一个回调函数,这个函数会在组件被加载完成或者卸载完成之后调用

这个 ref 属性其实也可以直接指定成字符串,比如:,然后可以通过在组件外可以通过 this.refs.nameText 去访问这个 DOM 节点,但是这么使用会有诸多的问题,比如该链接中出现的问题:issues,所以不推荐直接使用字符串形式,而是使用回调函数形式去使用它

  1. 类组件示例

    // component-ref-attr.html
    
    class CustomInput extends React.Component {
        constructor(props) {
            super(props);
    
            this.focus = this.focus.bind(this);
        }
    
        focus() {
            this.textInput.focus();
            this.textInput.value = 'I am focused.';
    
            console.log( this.textInput );
        }
    
        render() {
            return (
                <div>
                    "text"
                        ref={(input) =>{this.textInput = input;}}
                    />
                    "button" value="click me"
                        onClick={this.focus}
                    />
                div>
            );
        }
    }
    
    ReactDOM.render(
        ,
        document.getElementById('root')
    );
    

    点击按钮之前:

    点击按钮之后:

    看下控制台输出:

    结果显而易见,因为输入框被加载完成之后,会去执行

    ref={(input) =>{this.textInput = input;}}

    ref 里面的函数就是把当前对象(DOM树中的 input 元素对象缓存到了 this.textInput

    上面例子是直接在 HTML 元素上加的,其实对于组件也是一样的处理,就不多介绍了,来看下函数式组件定义里面怎么用这个 ref 绑定组件的

  2. 函数式组件示例

    // component-ref-attr.html
    
    function CustomFunctionalInput() {
    
        let textInput = null;
    
        function focus() {
    
            console.log( textInput )
    
            textInput.focus();
            textInput.value = 'I am focused.';
        }
    
        return (
            
    "text" ref={(input) =>{textInput = input;}} /> "button" value="click me" onClick={focus} />
    ); } ReactDOM.render( , document.getElementById('root') );

    从上面示例看,函数式组件使用 ref 的关键就是在组件内部定义一个缓存 ref 指向的组件的局部变量,实验结果和上例一样。

最后尝试了下在 React 组件之上添加 ref 去控制子组件里面的 HTML 元素,结果是还是需要组建内去另外维护一份自己的 ref,指向具体的 HTML 元素。

比如下面:

// component-ref-attr.html

// 子组件
class ButtonInput extends React.Component {
    constructor(props) {
        super(props);

        this.btnClick = this.btnClick.bind(this);
    }

    btnClick(e) {
        this.props.btnClick(e);
    }

    render() {
        return (
            <div>
                "text"
                />
                "button" 
                    value="click me"
                    onClick={this.props.btnClick}
                />
            div>
        );
    }
}

// 父组件
class CustomInput extends React.Component {
    constructor(props) {
        super(props);

        this.focus = this.focus.bind(this);
    }

    focus() {
        // this.textInput.focus();
        // this.textInput.value = 'I am focused.';

        console.log( this.btnInput );
    }

    render() {
        return (
             {this.btnInput = btnInput;}} 
                btnClick={this.focus}
            />
        );
    }
}

this.btnInput 输出到控制台得到对象如图

从控制台对象中可知并没有发现 DOM 树中的 input/textinput/button ,这是不是意味着并不能直接通过组件对象去获取 DOM 树中的实际元素对象。

如果非要使用 ref 特性去访问组件下的组件中的 DOM 元素,估计只能在被包含的组件中也去设置个 ref 属性,比如上例可修改如下:

ButtonInput 组件的按钮点击事件处理函数中添加如下:

btnClick(e) {
    this.props.btnClick(e);
    this.btnInput.focus();
    this.btnInput.value='I am focused by parent.';
}

然后 input 元素属性添加:

"text"
    ref={(btnInput) => {this.btnInput = btnInput;}}
/>

这样便可以实现,多级组件控制 html 元素,验证结果OK。

不可控组件,如:表单(Uncontrolled Components)

这里不可控组件指的是一些组件会有自己的一些默认行为,如表单提交动作,这个时候可能需要拦截提交动作,把表单中的数据进行处理之后再去提交,这个时候就需要使用到‘可控组件’了。

表单的可控组件关键在于提交事件拦截


// uncontrolled-component.html

class ControlledForm extends React.Component {
    constructor(props) {
        super(props);

        this.submitHandle = this.submitHandle.bind(this); 
    }

    submitHandle( e ) {
        this.nameTextInput.value = 'submit form';
        e.preventDefault(); 
    }

    render() {
        return (
            
this.submitHandle}>
); } }

对于表单来说,可控组件的实现,关键有两点:

  1. 通过 ref 属性去引用组件(其实也可以通过组件的状态值去设置)
  2. 阻止其默认行为,自定义处理事件

默认值:defaultValue

对于元素都有其默认的一些属性,比如:

type=radiotype=checkboxdefaultChecked
type=selecttype=textdefaultValue

大部分情况下使用的都是 defaultValue

会发现如果我们直接在元素上添加 value 属性并赋予值之后,其内容没法通过输入去修改,也就是说被固定了,只能通过代码去修改 ele.value 属性

但是如果给元素绑定了事件,比如:onChange,这个时候值就能发生改变了,这个在之前基础篇的时候有说过。

如果指定的是 value 不是 defaultValue 的话,就需要使用到 state 状态值,同时指定相应的事件处理。

class ControlledForm extends React.Component {
    constructor(props) {
        super(props);

        this.submitHandle = this.submitHandle.bind(this); 
    }

    submitHandle( e ) {
        this.nameTextInput.value = 'submit form';
        e.preventDefault(); 
    }

    render() {
        return (
            
this.submitHandle}>
); } }

如果用到 value 就需要结合 stateonChange 处理

// 修改点 1 : 构造器中添加状态对象
this.state = {
    nameText: 'lizc'
};

// 修改点 2 : 给输入框元素添加事件绑定
nameTextInputChange( e ) {
    this.setState({
        nameText: e.target.value
    });
}

// 修改点 3 : input 元素的 value 属性指定为状态中的值 this.state.nameText
value={this.state.nameText} onChange={this.nameTextInputChange}/>

总结

清明节刚过,整个人累的不要不要的,不过宝宝开心的不要不要的,深圳湾共享单车问题也是闹得不要不要的,不过我们是放假第一天就去了,很庆幸的躲过了这一坑,喜剧性的事情是节后第一天小区门口居然贴上了:“禁止单车入园区” 一告示,逗得不要不要的 ~~~~。

话说,也有几天没更新了,还是要坚持自己的选择一步一个脚印去实行,虽然近期换工作结果非常不理想(可能是工作履历的原因,也可能是自己没啥对方需要的实际经验,也可能是面试过程中表现不理想),总之自己的果自己尝,努力不放弃就对了。

状态还是有点灰心的,简历如实写了,连面试电话都接不到几个,打击还是有的,信心还是要时刻保持着,学习进度还是继续保持下去,毕竟回头路都没那么好走。

发泄了一丢丢小情绪小心情,该收拾收拾~~~

该篇文章算是承接上一篇《React 基础教程完整版》的下一学习篇的记录,这里涉及的内容主要涉及内容还是 props, state, refs 三个内容来讲述的,至于更多的有关性能优化,在不使用 ES6 或 JSX 时候的替代方法,等等只是看了个大概,感觉并没有太多记录的必要(毕竟还是要紧跟步伐,ES6 和 JSX 还是要实际使用起来,熟练运用)。

另外需要提的是关于 React 更新比较算法的问题,值得看看。

接下来的计划是针对状态管理的框架,比如:Redux 进行研究学习,结合 React 投入使用当中。

你可能感兴趣的:(ReactJS)