热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解form系列(二)React表单的优化

ReactForm在构建web应用的时候,为了采集用户输入,表单变成了我们不可或缺的东西。大型项目中,如果没有对表单进行很好的抽象和封装,随着表单复杂度和数量的增加,处理表单将会变

React Form

在构建 web 应用的时候,为了采集用户输入,表单变成了我们不可或缺的东西。大型项目中,如果没有对表单进行很好的抽象和封装,随着表单复杂度和数量的增加,处理表单将会变成一件令人头疼的事情。在 react 里面处理表单,一开始也并不容易。所以在这篇文章中,我们会介绍一些简单的实践,让你能够在 react 里面更加轻松的使用表单。如果你对 HTML 表单的基础掌握得不是太好,那么我建议你先阅读我的上一篇文章 深入理解 HTML 表单

好了,废话不多说,让我们先来看一个简单的例子。

示例

LoginForm.js

handleChange = evt => {
this.setState({
username: evt.target.value,
});
};
render() {
return (


type="submit"
value="Submit"
/>

);
}

在上面的例子中,我们创建了一个输入框,期望用户在点击 submit 之后,提交用户输入。

移步
这里

查看文章中的全部代码

数据的抽象

对于每一个表单元素来说, 除开 DOM 结构的不一样,初始值, 错误信息, 是否被 touched, 是否 valid,这些数据都是必不可少的。所以,我们可以抽象一个中间组件,将这些数据统一管理起来,并且适应不同的表单元素。这样 Field 组件 就应运而生了。

Field 作为一个中间层,包含表单元素的各种抽象。最基本的就是 Field 的名字对应的值
Field 不能单独存在,因为 Field 的 value 都是来自传入组件的 state, 传入组件通过 setState 更新 state, 使 Field 的 value 发生变化

Field: {
name: String, // filed name, 相当于上面提到的 key
value: String, // filed value
}

在实际情况中, 还需要更多的数据来控制 Field 的表现行为,比如 valid, invalid, touched 等。

Field:{
name: String, // filed name, 相当于上面提到的 key
value: String, // filed value
label: String,
error: String,
initialValue: String,
valid: Boolean,
invalid: Boolean,
visited: Boolean, // focused
touched: Boolean, // blurred
active: Boolean, // focusing
dirty: Boolean, // 跟初始值不相同
pristine: Boolean, // 跟初始值相同
component: Component|Function|String, // 表单元素
}

点这里了解 => Redux Form 对 Field 的抽象

UI的抽象

Field 组件

  1. 作为通用抽象, Field对外提供一致接口。 一致的接口能够使 Field 的使用起来更加的简单。比如更新 checkbox 的时候,我们更新的是它的 checked 属性而不是 value 属性,但是我们可以对 Field 进行封装,对外全部提供 value 属性,使开发变得更加容易。
  2. 作为中间层, Field可以起到拦截作用。 如先格式化传入的 value,再将这个 value 传递给下层的组件,这样所有下层组件得到的都是格式化之后的值。

Field.js

static defaultProps = {
component: Input,
};
render() {
const { component, noLabel, label, ...otherProps } = this.props;
return (

);
}

上面的例子是 Field 组件的简单实现。Field 对外提供了统一的 label 和 noLabel 接口,用来显示或不显示 label 元素。

Input 组件

创建Input 组件的关键点在于使它变得“可控”,也就是说它并不维护内部状态。关于可控组件,接下来会介绍。

Input.js

handleChange = evt => {
this.props.onChange(evt.target.value);
};
render() {
return (

);
}

看上面的代码,为什么不直接把 onChange 函数通过 props 传进来呢?就像下面这样

render() {
return (

);
}

其实是为了让我们从 onChange 回调中得到 统一的
value, 这样我们在外部就不用去 care 究竟是 取
event.target.value 还是
event.target.checked.

优化后的 LoginForm 如下:

LoginForm.js

class LoginForm extends Component {
state = {
username: '',
};
handleChange = value => {
this.setState({
username: value,
});
};
render() {
return (

label="username"
name="username"
value={this.state.username}
OnChange={this.handleChange}
/>
type="submit"
value="Submit"
/>

);
}
}

可控组件与不可控组件

可控组件与不可控组件最大的区别就是:对内部状态的维护与否

一个可控的 应该具有哪些特点?

  1. 通过 props 提供 value。可控组件并不维护自己的内部状态,也就是外部提供什么,就显示什么,所以组件能够通过 props 很好的控制起来
  2. 通过 onChange 更新value。

type="text"
value={this.props.username}
OnChange={this.handleChange}
/>

点这里了解 => React 可控组件与不可控组件

使用 React 高阶组件进一步优化

在 LoinForm.js 中可以看到,我们对 setState 操作的依赖程度很高。如果在 form 中多添加一些 Field 组件,不难发现对于每一个 Field,都需要重复 setState 操作。过多的 setState 会我们的Form 组件变得不可控,增加维护成本。

仔细观察上面的代码,不难发现,在每一次 onChange 事件中,都是通过一个 keyvalue更新到 state 里面。比如上面的例子中,我们是通过 username 这个 key 去更新的。所以不难想到,利用高阶组件,可以不用在 LoginForm 里面维护内部状态。

高阶组件在这里就不再展开了,我会在接下来的文章中专门来详细介绍这一部分内容。

withState.js

const withState = (stateName, stateUpdateName, initialValue) =>
BaseCompOnent=>
class extends Component {
state = {
stateValue: initialValue,
};
updateState = (stateValue) => {
this.setState({
stateValue,
});
};
render() {
const { stateValue } = this.state;
return createElement(BaseComponent, {
...this.props,
[stateName]: stateValue,
[stateUpdateName]: this.updateState,
});
}
};

除了 state 之外,我们可以将
onChange,
onSubmit 等事件处理函数也 extract 出去,这样可以进一步简化我们的 Form。

withHandlers.js

const withHandlers = handlers => BaseCompOnent=>
class WithHandler extends Component {
cachedHandlers = {};
handlers = mapValues(
handlers,
(createHandler, handlerName) => (...args) => {
const cachedHandler = this.cachedHandlers[handlerName];
if (cachedHandler) {
return cachedHandler(...args);
}
const handler = createHandler(this.props);
this.cachedHandlers[handlerName] = handler;
return handler(...args);
}
);
componentWillReceiveProps() {
this.cachedHandlers = {};
}
render() {
return createElement(BaseComponent, {
...this.props,
...this.handlers,
});
}
};

使用高阶组件改造后的 LoginForm 如下:

LoginForm.js

const withLoginForm = _.flowRight(
withState('username', 'onChange', ''),
withHandlers({
onChange: props => value => {
props.onChange(value);
},
onSubmit: props => event => {
event.preventDefault();
console.log(props.username);
},
})
);
@withLoginForm
class LoginForm extends Component {
static propTypes = {
username: PropTypes.string,
onChange: PropTypes.func,
onSubmit: PropTypes.func,
};
render() {
const { username, onChange, onSubmit } = this.props;
return (

label="username"
name="username"
value={username}
OnChange={onChange}
/>
type="submit"
value="Submit"
/>

);
}
}

通过
compose
withState
withHandler 组合起来, 并应用到 Form 之后,跟之前比起来,LoginForm 已经简化了很多。LoginForm 不再自己维护内部状态,变成了一个完完全全的可控组件,不管是之后要对它写测试还是要重用它,都变得十分的轻松了。

点这里了解 => Recompose

结语

对于复杂的项目来说,以上的抽象还远远不够,在下一篇文章中,会介绍如何进一步让你的 Form 变得更好用。


推荐阅读
  • 点击后defaultEducation的值明明改变了,但props传给子组件却watch不到 ... [详细]
  • DOM事件大全
    1.事件:js与html的交互就是通过事件的,观察者模式2.事件流:从页面中接收事件的顺序IE::事件冒泡流,事件冒泡,事件从最具体的元素接收,然后逐级向上传播,主流浏览器都支持N ... [详细]
  • 前端库Bootstrap框架:「11]使用 span 创建行内元素
    前端库Bootstrap框架:「11]使用 span 创建行内元素 ... [详细]
  • Therearelotsofdesignpatternsareavailablefordevelopingtheasp.netsite.Everyonedesignpa ... [详细]
  • 本文介绍了停用Vaadin焦点颜色的全局方法。焦点环是一种辅助功能,用于指示字段已从键盘交互获得焦点。每个组件和主题的焦点环样式不同。文章提供了一种方便的方法来找到和修改焦点环样式,通过检查shadow DOM中的标签并覆盖相应的样式。同时,还介绍了使用with或导入样式表的方法来应用修改后的样式。 ... [详细]
  • iOS之富文本
    之前做项目时遇到一个问题:使用UITextView显示一段电影的简介,由于字数比较多,所以字体设置的很小,行间距和段间距也很小,一大段文字挤在一起看起来很别扭,想要把行间距调大,结 ... [详细]
  • 在这一期的SendMessage函数应用中,我将向大家介绍如何利用消息函数来扩展树型列表(TreeView)控件的功能相信对于树型列表控件大家十分的熟悉, ... [详细]
  • 在写每日签到的时候,我居然使用的是本地时间被项目经理笑哭了。。。。,如果你在写单机游戏,没有游戏服务器,但又不想使用本地时间,就可以采用下面方法.方法总结:     1.使用 ... [详细]
  • 上次我们总结了React代码构建后的webpack模块组织关系,今天来介绍一下Babel编译JSX生成目标代码的一些规则,并且写一个简单的解析器,模拟整个生成的过程。我们还是拿最简 ... [详细]
  • 一、使用ContentProvider(内容提供者)共享数据ContentProvider在android中的作用是对外共享数据,也就是说 ... [详细]
  • 技术点:1、通过已知的网页路径获得流2、把流转换成字节数组3、把字节数组转换成String字符串显示在TextView控件中一、获得流publicstaticSt ... [详细]
  • 本文整理了Java中com.google.gwt.user.client.ui.RootPanel.detachOnWindowClose方法的一些代码示例,展示了 ... [详细]
  • SimpleDateFormat类所在java包位置:java.text.SimpleDateFormat。继承结构如下:复制代码java.lang. ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • WPF之Binding初探
      初学wpf,经常被Binding搞晕,以下记录写Binding的基础。首先,盗用张图。这图形象的说明了Binding的机理。对于Binding,意思是数据绑定,基本用法是:1、 ... [详细]
author-avatar
索马里7_244
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有