您的位置:首页 > Web前端 > React

React-高级教程完整版

2017-04-05 23:01 204 查看

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(
<TypeCheck sayHello={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(
<TypeCheck sayHello='Hello world!' />,
document.getElementById('root')
);


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

defaultProps
默认属性值对象

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

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


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

Refs
DOM
(Refs and the DOM)

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

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

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

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

ref
使用时机

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


管理组件焦点(focus),文本选择(text selection)或者媒体播放(media playback)

触发必须执行的动画

集成第三方
DOM


DOM
元素添加
ref
(引用)

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

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

类组件示例

// 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>
<input
type="text"
ref={(input) =>{this.textInput = input;}}
/>
<input
type="button" value="click me"
onClick={this.focus}
/>
</div>
);
}
}

ReactDOM.render(
<CustomInput />,
document.getElementById('root')
);


点击按钮之前:



点击按钮之后:



看下控制台输出:



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

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


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


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

函数式组件示例

// component-ref-attr.html

function CustomFunctionalInput() {

let textInput = null;

function focus() {

console.log( textInput )

textInput.focus();
textInput.value = 'I am focused.';
}

return (
<div>
<input
type="text"
ref={(input) =>{textInput = input;}}
/>
<input
type="button" value="click me"
onClick={focus}
/>
</div>
);
}

ReactDOM.render(
<CustomFunctionalInput />,
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>
<input
type="text"
/>
<input
type="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 (
<ButtonInput
ref={(btnInput) => {this.btnInput = btnInput;}}
btnClick={this.focus}
/>
);
}
}


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



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

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

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

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


然后
input
元素属性添加:

<input
type="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 (
<form onSubmit={this.submitHandle}>
<label>
<input
type="text"
ref={(input) => {this.nameTextInput = input;}}
/>
<input type="submit" value="submit" />
</label>
</form>
);
}
}


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

通过
ref
属性去引用组件(其实也可以通过组件的状态值去设置)

阻止其默认行为,自定义处理事件

默认值:
defaultValue

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

type=radio
type=checkbox
defaultChecked


type=select
type=text
defaultValue


大部分情况下使用的都是
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 (
<form onSubmit={this.submitHandle}>
<label>
<input
type="text"
defaultValue="lizc"
ref={(input) => {this.nameTextInput = input;}}
/>
<input type="submit" value="submit" />
</label>
</form>
);
}
}


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

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

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

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


总结

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

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

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

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

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

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

接下来的计划是针对状态管理的框架,比如:
Redux
进行研究学习,结合
React
投入使用当中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  react javascript state ES6 JSX