react对原生事件进行了一次封装,命名为 SyntheticEvent,具备和原生事件相同的属性
通用属性 bubbles (boolean) 表示事件是否冒泡 cancelable(boolean) 表示事件是否可以取消 currentTarget(DOMEventTarget) 与Target类似,由于事件可以冒泡,所以两者表示的内容是不同的 defaultPrevented(boolean) 表示事件是否禁止了默认行为 eventPhase(number) 表示事件所处的阶段 isTrusted(boolean) 表示事件是否可信。所谓的可信事件表示的是用户操作的事件,不可信事件就是通过JS代码来触发的事件。 nativeEvent(DOMEvent) preventDefault() (void) 对应的defaultPrevented,表示的是禁止默认行为 stopPropagaTion() (void) 对应的是bubbles,表示的是sh target(DOMEventTarget) timeStamp(number) 时间戳,也就是事件触发的事件 type(string) 事件的类型 特殊属性举例 鼠标事件的 clientX 和 clientY 键盘事件的 key ... 冒泡特性举列例如有一个登陆框,我们想要在点击登陆框以外的范围时将登陆框关闭
var LoginDialog =React.createClass({ getInitialState: function() { return { claz: '', text: '' }; }, handleClick(e){ console.log('click block!'); e.nativeEvent.stopImmediatePropagation(); //IE9+ }, render: function() { return ( <div className={this.state.claz} onClick={this.handleClick}> <input value={this.state.text} onChange={(e)=>{this.setState({text: e.target.value})}} /> </div> ); }, componentDidMount: function(){ document.addEventListener('click', () => { console.log('body'); this.setState({claz: 'fd-hide'}); }); 验证事件是代理在 document 上的 // document.body.addEventListener('click', () => { // console.log('body'); // this.setState({claz: 'fd-hide'}); // }); } }); ReactDOM.render(<LoginDialog/>, document.getElementById('fe'));我们先在 document 上添加 click 事件处理 LoginDialog 的隐藏,然后在 LoginDialog 中也监听 click 事件,然后利用 stopImmediatePropagation 阻止相同元素上的同类型事件,就能实现需求的效果。 可见 react 的事件都是代理在 document 上的。由上面代码可以看出,如果我们把事件监听放在 document.body 上时,LoginDialog 中的阻止同类型事件触发就失效了。
SyntheticEvent 对象会在处理完判断 isPersistent,为 false 就释放对象
//ResponderEventPlugin if (!terminationRequestEvent.isPersistent()) { terminationRequestEvent.constructor.release(terminationRequestEvent); }release 的相关实现,它会调用对象的 destructor 方法
//react.js line:143 var standardReleaser = function (instance) { var Klass = this; !(instance instanceof Klass) ? "development" !== 'production' ? invariant(false, 'Trying to release an instance into a pool of a different type.') : _prodInvariant('25') : void 0; instance.destructor(); if (Klass.instancePool.length < Klass.poolSize) { Klass.instancePool.push(instance); } };SyntheticEvent 的析构函数实现,将属性置为了 null
//SyntheticEvent destructor: function() { var Interface = this.constructor.Interface; for (var propName in Interface) { if (__DEV__) { Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName])); } else { this[propName] = null; } } for (var i = 0; i < shouldBeReleasedProperties.length; i++) { this[shouldBeReleasedProperties[i]] = null; } if (__DEV__) { Object.defineProperty( this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null) ); Object.defineProperty( this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', emptyFunction) ); Object.defineProperty( this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', emptyFunction) ); } },而 SyntheticEvent 中 persist 方法做的就是将 isPersistent 变为一个返回 true 的函数
//SyntheticEvent /** * We release all dispatched `SyntheticEvent`s after each event loop, adding * them back into the pool. This allows a way to hold onto a reference that * won't be added back into the pool. */ persist: function() { this.isPersistent = emptyFunction.thatReturnsTrue; }, /** * Checks if this event should be released back into the pool. * * @return {boolean} True if this should not be released, false otherwise. */ isPersistent: emptyFunction.thatReturnsFalse,表单是运用事件的一个很好的场景, 我们来看一个基本的表单实现
var FormExample = React.createClass({ getInitialState: function(){ return { name: 'rube', password: '123456', selected: '1' } }, handleNameChanged: function(event){ this.setState({name: event.target.value}); }, handlePwdChanged: function(event){ this.setState({password: event.target.value}) }, handleSelectedChanged: function(event){ this.setState({selected: event.target.value}); }, render: function(){ console.log('render'); return ( <form> <input value={this.state.name} onChange={this.handleNameChanged}/> <input value={this.state.password} onChange={this.handlePwdChanged}/> <select value={this.state.selected} onChange={this.handleSelectedChanged}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </form> ); } }); ReactDOM.render(<FormExample/>, document.getElementById('example'));如果我们运行上面的代码会发现,render 会输出很多次,每一次 state 变动都会触发重新 render,这可能会对性能造成一定的影响。 同时这样的表单在表单值控制,表单校验的能力上都较弱,因此我们看看有没有什么更好地实现
运行这个实例上看,我们直接改变了 InputComponent 的 state 从而不触发外部 form 的 render, 相关数据和操作由 context 传入,能够很好地对验证时机和表单数据进行控制
react Context 相关介绍 http://reactjs.cn/react/docs/context.html
redux-form 用 Field 来替代了每个输入域,那我们重点来看下 Field
//redux-form/src/Field.js line:91 //每个 Field 其实最终会 render 一个 ConnectedField render() { return createElement(ConnectedField, { ...this.props, name: this.name, normalize: this.normalize, _reduxForm: this.context._reduxForm, ref: 'connected' }) } //redux-form/src/ConnectedField.js line:134 const connector = connect( (state, ownProps) => { const { name, _reduxForm: { initialValues, getFormState } } = ownProps const formState = getFormState(state) const initialState = getIn(formState, `initial.${name}`) const initial = initialState !== undefined ? initialState : (initialValues && getIn(initialValues, name)) const value = getIn(formState, `values.${name}`) const submitting = getIn(formState, 'submitting') const syncError = getSyncError(getIn(formState, 'syncErrors'), name) const syncWarning = getSyncWarning(getIn(formState, 'syncWarnings'), name) const pristine = value === initial return { asyncError: getIn(formState, `asyncErrors.${name}`), asyncValidating: getIn(formState, 'asyncValidating') === name, dirty: !pristine, pristine, state: getIn(formState, `fields.${name}`), submitError: getIn(formState, `submitErrors.${name}`), submitting, syncError, syncWarning, value, _value: ownProps.value // save value passed in (for checkboxes) } }, undefined, undefined, { withRef: true } ) return connector(ConnectedField)ConnectedField 会关联 _reduxForm 的 initialValues, getFormState, 看看 _reduxForm 从何而来,是用 Context 从上层传递下来的,详见如下代码
//redux-form/src/reduxForm.js line:120 getChildContext() { return { _reduxForm: { ...this.props, getFormState: state => getIn(this.props.getFormState(state), this.props.form), asyncValidate: this.asyncValidate, getValues: this.getValues, sectionPrefix: undefined, register: this.register, unregister: this.unregister, registerInnerOnSubmit: innerOnSubmit => this.innerOnSubmit = innerOnSubmit } } }总体下来看,redux-from 和我们自己上面自己实现的那个高级一点的 form 原理是不是微微有点像呢~,它也是将一个 _reduxForm 由 Context 传递到子组件然后将子组件的 state 托管到 _reduxForm 里面 具体的校验什么的能力就不展开了,具体查看官方 demo http://redux-form.com/
