这标题可能有点不太贴切或符合内容,从官方上来区分这部分内容确实属于高级部分,只是由于个人原因,在后面的一些章节并没有记录在列。
也为了承接上一篇,因此勉强将标题定位:“React 高级教程完整版”
纯属针对个人学习记录成果,无他~~~
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'
};
指定之后,如果没有传入该属性则会启用该默认属性值。
Refs
和 DOM
(Refs and the DOM)在典型的 React
数据流中, props
是唯一一种能让父组件与子组件进行沟通的方式,比如去修改子组件,刷新重绘子组件都需要用到 props
属性对象;但是,有些情况必须要在组件外部或者组件渲染完成之后去引用它,那这个时候 props
就显得无能为力了,因为这个属性对象只能在组件内部使用。
因此就有 refs
这个东西,其实更具体点,这个 ref
对象可以视为未渲染或者渲染之后的组件对象
如果没有渲染那么代表的是 react
组件,
如果组件被渲染之后 ref
指向了 DOM 书中的该组件对象;
ref
使用时机
如果能用属性来控制的,尽量避免使用 ref
。
DOM
库给 DOM
元素添加 ref
(引用)
在定义组件的时候,可以给组件添加一个 ref
属性,这个属性值是一个回调函数,这个函数会在组件被加载完成或者卸载完成之后调用
这个
ref
属性其实也可以直接指定成字符串,比如:,然后可以通过在组件外可以通过
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>
"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
绑定组件的
函数式组件示例
// 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/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
元素属性添加:
"text"
ref={(btnInput) => {this.btnInput = btnInput;}}
/>
这样便可以实现,多级组件控制 html
元素,验证结果OK。
这里不可控组件指的是一些组件会有自己的一些默认行为,如表单提交动作,这个时候可能需要拦截提交动作,把表单中的数据进行处理之后再去提交,这个时候就需要使用到‘可控组件’了。
表单的可控组件关键在于提交事件拦截
// 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 (
);
}
}
对于表单来说,可控组件的实现,关键有两点:
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 (
);
}
}
如果用到 value
就需要结合 state
和 onChange
处理
// 修改点 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
投入使用当中。