- 鼠标类事件:onClick, onMouseOver ...
- 拖拽类事件:onDrop, onDrag ...
- 触摸类事件:onTouchMove, onTouchStart ...
- 键盘事件:onKeyDown, onKeyUp ...
- 剪切类事件:onCopy, onPaste ...
- 表单类事件:onSubmit, onChange ...
- .
- .
- .
- var EventExample01 = React.createClass({
- getInitialState: function() {
- return {
- text: ''
- };
- },
- handleClick: function() {
- this.setState({
- text: this.state.text ? '': 'hello world'
- });
- },
- render: function() {
- return ( < div onClick = {
- this.handleClick
- } > <div > {
- this.state.text
- } < /div>
- </div > );
- }
- });
- ReactDOM.render( < EventExample01 / >, document.getElementById('example01'));
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 中的阻止同类型事件触发就失效了。
- //原生事件 example
- var exampleElement = document.getElementById('example');
- exampleElement.addEventListener('click',
- function(e) {
- setTimeout(function() {
- console.log(e.target);
- },
- 0);
- });
- //SyntheticEvent example
- var SyntheticEventExample = React.createClass({
- handleClick: function(e) {
- setTimeout(function() {
- console.log(e.target);
- },
- 0);
- },
- render: function() {
- return ( < div id = "example"onClick = {
- this.handleClick
- } > </div>
- );
- }
- });
- ReactDOM.render(<SyntheticEventExample/ > , document.getElementById('example01'));
- //对比两个的输出会发现原生的 example 能够输出 e.target,因为它的引用还在
- //SyntheticEvent example 的输出为 null, 因为已经释放
- var SyntheticEventExample = React.createClass({
- handleClick: function(e) {
- e.persist();
- setTimeout(function() {
- console.log(e.target);
- },
- 0);
- },
- render: function() {
- return ( < div id = "example"onClick = {
- this.handleClick
- } > </div>
- );
- }
- });
- ReactDOM.render(<SyntheticEventExample/ > , document.getElementById('example01'));
- //加上 e.persist(); 后能够输出 e.target
- //why?
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,这可能会对性能造成一定的影响。
同时这样的表单在表单值控制,表单校验的能力上都较弱,因此我们看看有没有什么更好地实现
- var InputComponent = React.createClass({
- contextTypes: {
- rubeform: React.PropTypes.object
- },
- getInitialState: function(){
- return {
- value: this.context.rubeform.data[this.props.name]
- }
- },
- render: function(){
- return (
- <div>
- <input value={this.state.value} onChange={this.context.rubeform.change.bind(this, this)}/>
- </div>
- );
- }
- });
- var RubeForm = React.createClass({
- render: function(){
- console.log('render');
- return (
- <form onSubmit={this.props.submit}>
- <InputComponent name='a'/>
- <InputComponent name='b'/>
- <InputComponent name='c'/>
- <InputComponent name='d'/>
- <input type='submit' value='submit'/>
- </form>
- )
- }
- });
- var FormExample02 = React.createClass({
- childContextTypes: {
- rubeform: React.PropTypes.object,
- },
- data: {
- a: 'default hello a',
- b: 'default hello b',
- c: 'default hello c',
- d: 'default hello d'
- },
- getChildContext: function(){
- var _this = this;
- return {
- rubeform: {
- change: function(){
- this.setState({value: arguments[1].target.value});
- _this.data[this.props.name] = arguments[1].target.value;
- },
- data: _this.data
- }
- }
- },
- render: function(){
- return (
- <RubeForm submit={this.onSubmit}/>
- );
- },
- onSubmit: function(e){
- window.alert(`You submitted:\n\n${JSON.stringify(this.data, null, 2)}`)
- e.preventDefault();
- }
- });
- ReactDOM.render(<FormExample02/>, document.getElementById('example'));
运行这个实例上看,我们直接改变了 InputComponent 的 state 从而不触发外部 form 的 render, 相关数据和操作由 context 传入,
能够很好地对验证时机和表单数据进行控制
react Context 相关介绍
- import React from 'react'
- import { Field, reduxForm } from 'redux-form'
- const SimpleForm = (props) => {
- const { handleSubmit, pristine, reset, submitting } = props
- return (
- <form onSubmit={handleSubmit}>
- <div>
- <label>First Name</label>
- <div>
- <Field name="firstName" component="input" type="text" placeholder="First Name"/>
- </div>
- </div>
- <div>
- <label>Last Name</label>
- <div>
- <Field name="lastName" component="input" type="text" placeholder="Last Name"/>
- </div>
- </div>
- <div>
- <label>Email</label>
- <div>
- <Field name="email" component="input" type="email" placeholder="Email"/>
- </div>
- </div>
- <div>
- <label>Sex</label>
- <div>
- <label><Field name="sex" component="input" type="radio" value="male"/> Male</label>
- <label><Field name="sex" component="input" type="radio" value="female"/> Female</label>
- </div>
- </div>
- <div>
- <label>Favorite Color</label>
- <div>
- <Field name="favoriteColor" component="select">
- <option></option>
- <option value="ff0000">Red</option>
- <option value="00ff00">Green</option>
- <option value="0000ff">Blue</option>
- </Field>
- </div>
- </div>
- <div>
- <label htmlFor="employed">Employed</label>
- <div>
- <Field name="employed" id="employed" component="input" type="checkbox"/>
- </div>
- </div>
- <div>
- <label>Notes</label>
- <div>
- <Field name="notes" component="textarea"/>
- </div>
- </div>
- <div>
- <button type="submit" disabled={pristine || submitting}>Submit</button>
- <button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
- </div>
- </form>
- )
- }
- export default reduxForm({
- form: 'simple' // a unique identifier for this form
- })(SimpleForm)
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
来源: