京东农行京东申请退款多久到账账

966,690 三月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
让React组件变得可响应
让React组件变得可响应
日. 估计阅读时间:
欲知区块链、VR、TensorFlow等潮流技术和框架,请锁定
我们将创建的Snapterest是一个Web应用程序,它会实时接收来自Snapkite引擎服务器的推文,并将推文逐条显示给用户。虽然我们不知道Snapterest会在什么时候收到一条新推文,但是当新推文到达时,它至少应该显示1.5秒钟以便用户有足够的时间看到并点击它。点击推文会将它添加到一个现有的推文集合或者创建一个新的集合。最终用户能够将集合导出为一段HTML代码。
对于我们正在构建的应用,上述描述非常笼统。让我们把它分解成更小的任务,如下图:
实时地从Snapkite引擎服务器接收推文。
每条推文至少显示1.5秒。
通过用户的点击事件将推文添加到一个集合中。
显示集合中的推文。
为集合创建HTML代码并导出。
通过用户点击事件从集合中移除推文。
相关厂商内容
你可以确定哪些任务能使用React解决吗?记住React是一个用户界面库,因此,任何与用户界面或用户界面交互相关的任务都可以使用React解决。在前面的列表中,除了第一个任务外,其他的任务React都能胜任,因为第一个任务描述的是数据获取,与用户界面毫无关系。任务1将会使用其他库来解决,我们将在下一章讨论。任务2和任务4描述的内容是需要被显示的,React组件是最合适的选择。任务3和任务6描述的是用户事件,如我们在第3章所介绍的,用户事件处理可以被很好地封装在React组件中。任务5怎样使用React来解决呢?第2章我们讨论过ReactDOMServer.renderToStaticMarkup()方法能将React元素渲染成静态的HTML标记字符串,这正是我们解决任务5所需的方案。
现在我们已经为每一个任务确定了潜在的解决方案,让我们思考一下我们将要怎么把它们结合在一起来创建一个功能全面的Web应用程序。
有两种方法来创建可组合的React应用:
先构建单个的React组件,然后将它们组合起来形成更高层级的React组件,自底向上来构建层级。
从最顶级的React元素开始,然后实现它的子组件,自顶向下来构建层级。
从观察和理解应用架构的角度来看,第二种策略更有优势。我认为在考虑各个部分的功能如何实现之前,先了解所有组件如何组合在一起更重要。
2. 规划React应用程序
规划React应用时应遵循下面两条简单的原则:
每个React组件应该代表一个用户界面元素。它应该封装最小的可复用元素。
多个React组件应该组成一个独立的React组件。最终,整个用户页面应该封装成一个React组件。
参见下图,先从最上层的React组件Application开始。它将封装我们的整个React应用程序,它有两个子组件:Stream和Collection。Stream组件将负责连接到一个消息流,接收和显示最新的消息。Stream组件有两个子组件:StreamTweet和Header。StreamTweet组件将负责显示最新的消息,它由Header和Tweet组合而成。Header组件将会渲染头部,它没有子组件。Tweet组件会渲染来自推文的一张图片。注意我们已经可以复用Header组件两次了。
我们的React组件的层次图
Collection组件负责显示收集控件和推文列表。它有两个子组件:CollectionControls和tweetlist。前者又有两个子组件:Collection RenameForm组件将渲染一个表单,用来重命名集合;CollectionExportForm组件将渲染一个表单,用来将集合导出到一个叫作CodePen的服务,这是一个HTML、CSS和JavaScript的演示网站,可以在上了解更多关于Codepen的信息。你可能已经注意到,我们将在CollectionFenameForm和CollectionControls组件中复用Header和Button组件。TweetList组件将渲染一个推文列表。每一条推文将被渲染成一个Tweet组件。在Collection组件中,我们将再次复用Header组件。事实上,我们总共要复用5次Header组件。这对我们来说能省很大事。正如我们在前一章中讨论的,我们应该尽可能保持更多组件是无状态的。因此,总共11个组件中只有下面5个组件存储状态:
Application
CollectionControls
CollectionRenameForm
?StreamTweet
有了规划之后,我们开始实现吧。
3. 创建一个React组件容器
让我们首先编辑应用程序的JavaScript主文件,使用下面的代码片段替换~/snapterest/source/app.js文件内容:var React = require('react');
var ReactDOM = require('react-dom');
var Application = require('./components/Application.react');
ReactDOM.render(&Application /&,
document.getElementById('react-
application'));
这个文件仅有四行代码,实现的是:将document.getElementById('react- application') 作为组件的部署目标,并将Application组件渲染到DOM中。Web应用程序的整个用户界面都将被封装在这个组件中。
接下来,切换到~/snapterest/source/components/目录并创建Applica tion.react.js文件。我们约定所有React组件的文件名都以react.js结尾,这样我们就可以很轻意地分辨React与非React文件。
让我们看一下Application.react.js文件的内容:var React = require('react');
var Stream = require('./Stream.react');
var Collection = require('./Collection.react');
var Application = React.createClass({
getInitialState: function() {
collectionTweets: {}
addTweetToCollection: function(tweet) {
var collectionTweets = this.state.collectionT
collectionTweets[tweet.id] =
this.setState({
collectionTweets: collectionTweets
removeTweetFromCollection: function(tweet) {
var collectionTweets = this.state.collectionT
delete collectionTweets[tweet.id];
this.setState({
collectionTweets: collectionTweets
removeAllTweetsFromCollection: function() {
var collectionTweets = this.state.collectionT
delete collectionTweets[tweet.id];
this.setState({
collectionTweets: {}
removeAllTweetsFromCollection: function () {
this.setState({
collectionTweets: {}
render: function() {
&div className="container-fluid"&
&div className="row"&
&div className="col-md-4 text-center"&
&Stream onAddTweetToCollection={this.addTweetToCollection} /&
&div className="col-md-8"&
&Collection
tweets={this.state.collectionTweets}
onRemoveTweetFromCollection={this.
removeTweetFromCollection}
onRemoveAllTweetsFromCollection={this.
removeAllTweetsFromCollection}/&
module.exports = A
这个组件的代码比app.js多了很多,但是这些代码可以很容易地分为三个逻辑部分:
引入依赖模块
定义React组件
作为模块导出这个React组件
大多数React组件中都可以看到这样的逻辑分割,因为包装成CommonJS模块才能使用Browserify引入它们。事实上,这个源文件的第一部分和第三部分的写法都是CommonJS规定的,与React无关。使用这种模块规范的目的是将应用程序分解成模块以便复用。因为React组件和CommonJS模块都可以封装代码并使代码更灵活,所以它们在一起自然可以很好地工作。将最终的用户界面逻辑封装在一个CommonJS模块形式的React组件中,其他模块就可以复用这个被封装好的React组件了。
Application.react.js文件的引入逻辑使用require()函数引入了依赖模块:var React = require('react');
var Stream = require('./Stream.react');
var Collection = require('./Collection.react');
这里Application组件引入了下面两个子组件:
Stream组件将在用户界面中渲染信息流部分。
Collection组件将在用户页面中渲染集合部分。
我们也需要引入React库,但这部分代码都是按照CommonJS模块规范编写的,与React本身无关。
Application.react.js文件的第二部分逻辑创建带有以下方法的ReactApp licaton组件:
getInitialState()
addTweetToCollection()
removeTweetFromCollection()
removeAllTweetsFromCollection()
只有getInitialState()和render()方法是React API,其他方法都是这个组件封装的应用程序逻辑的一部分。讨论完这个组件的render()方法会渲染什么内容之后,我们再仔细分析每个逻辑方法:render: function () {
&div className="container-fluid"&
&div className="row"&
&div className="col-md-4 text-center"&
&Stream onAddTweetToCollection={this.addTweetToCollection} /&
&div className="col-md-8"&
&Collection
tweets={this.state.collectionTweets}
onRemoveTweetFromCollection={this.
removeTweetFromCollection}
onRemoveAllTweetsFromCollection={this.
removeAllTweetsFromCollection} /&
这段代码使用Bootstrap框架定义了网页布局。如果你不熟悉Bootstrap,我强烈推荐你访问上的文档。掌握了这个框架你就能用最快的速度和最简单的方法搭建用户界面原型。不过即使你不知道Bootstrap,也不影响理解后面的内容。我们将网页划分为两列:一个小的和一个大的。小的包含Stream组件,大的包含Collection组件。可以想象我们的网页被划分成两个不等的部分,它们都包含React组件。
我们这样使用Stream组件:
&Stream onAddTweetToCollection={this.addTweetToCollection} /&
Stream组件有一个onAddTweetToCollection属性,Application组件将自己的addTweetToCollection()函数作为这个属性的值。addTweetTocollection()函数会添加一条推文到集合中。这是Applicaton组件中的一个自定义方法,我们可以用this关键字来引用它。
让我们看一下addTweetToCollection()做了什么:addTweetToCollection: function (tweet) {
var collectionTweets = this.state.collectionT
collectionTweets[tweet.id] =
this.setState({
collectionTweets: collectionTweets
&div class="md-section-divider"&&/div&
这个函数引用存储在当前state中的CollectionTweets,添加一条新推文到CollectonTweets对象,并通过调用setState()函数来更新state。在Stream组件中,当addTweetToCollection()函数被调用时,一条新推文会作为参数被传入。这是一个子组件更新其父组件state的例子。
这是React的一个重要机制,它的工作过程如下。
父组件传递一个回调函数作为子组件的属性。子组件可以通过this.props变量访问这个回调函数。
每当子组件想要更新父组件的state时,它就会调用这个回调函数并传递所有必要的数据到父组件的新状态中。
父组件更新它的state,而且state更新会触发render()函数重新渲染所有必要的子组件。
这就是React中父组件与子组件的交互机制。这个机制允许子组件将应用程序状态管理委托到它的父组件,子组件只需要关心如何渲染自己就行了。了解了这个机制之后,我们还将多次使用它,因为大部分React组件要保持无状态。应该只有少量的父组件负责存储和管理应用程序的state。这个最佳实践允许我们按照以下两个不同的关注点来有序地组织React组件:
管理应用程序的state和渲染。
只关注渲染并且将应用程序的state管理委托到父组件上。
Application组件的第二个子组件Collection如下:&Collection
tweets={this.state.collectionTweets}
onRemoveTweetFromCollection={this.removeTweetFromCollection}
onRemoveAllTweetsFromCollection={this.removeAllTweetsFromCollection}
&div class="md-section-divider"&&/div&
这个组件有如下一些属性。
tweets:引用当前推文集合。
onRemoveTweetFromCollection:这个函数从集合中删除特定的推文
onRemoveAllTweetFromCollection:这个函数从集合中删除所有的推文。
Collection组件的属性仅仅关注下面两点:
如何访问应用程序的state。
如何改变应用程序的state。
显然,onRemoveTweetFromCollection和onRemoveAllTweetsFromCollection函数允许Collection组件改变Application组件的state。另一方面,tweets属性把Application组件的state传递给Collection组件,使Collection组件获得访问state的只读权限。
你能觉察到在Application和Collection组件之间的数据的单向流动吗?以下是它的工作过程:
使用Application组件的getInitalState()方法初始化collection Tweets数据。
collectionTweets数据作为tweets属性传递给Collection组件。
Collection组件调用removeTweetFromCollection和removeAllTweet FromCollection,更新Application中的collectionTweets数据,然后再次开始循环。
注意,Collection组件不能直接改变Application组件的state。Collection组件有通过this.props对象访问state的只读权限,并仅可以通过调用父组件传递的回调函数来更新父组件的state。在Collection组件中,这些回调函数是this.props.onRemoveTweetFromCollection和this.props.onRemoveAllTweetFrom Collection。
在React组件层次中,这种数据流动的简单思维模型有助于增加组件的数量,而不增加用户页面的复杂性。比如,它可以有10个层级的React组件嵌套,如下图所示。
在这个层次结构中,如果组件G要改变根组件A的state,其所用的方法与组件B、组件F或者其他任何组件使用的方法完全相同。然而,在React中,你不应该将数据直接从组件A传递到组件G。相反,你首先可以把它传递给组件B,然后给组件C,然后给组件D,依此类推,直至组件G。组件B到组件F必须携带一些transit属性,这些属性实际上只对组件G有用。这看起来可能是在浪费时间,但是这个设计使我们更容易调试应用程序,并可以推理出它是如何工作的。想优化应用程序的架构总是有办法的。Flux就是一种优化方案,本书后面会讨论它。
最后,再看一下改变Application的state的两个方法:removeTweetFromCollection: function (tweet) {
var collectionTweets = this.state.collectionT
delete collectionTweets[tweet.id];
this.setState({
collectionTweets: collectionTweets
&div class="md-section-divider"&&/div&
removeTweetFromCollection()方法从存储在Application组件的推文集合中移除一条推文,它需要从组件的state中得到当前的collectionTweet对象,然后根据给定的ID从state对象中删除一条推文,并使用一个新的collectionTweets对象来更新组件的state。
此外removeAllTweetsFromCollection()方法会从组件state中移除所有推文:removeAllTweetsFromCollection: function () {
this.setState({
collectionTweets: {}
这些方法都是在子组件Collection中调用的,因为该组件没有其他方法可以改变Application组件的state。
在这一章中,我们学习了如何使用React解决问题。我们首先把问题分解成一些小问题,并讨论了如何使用React解决它们。然后,创建一些需要实现的React组件。最后,我们创建了第一个可组合的React组件,并学习了如何让父组件与它的子组件交互。在下一章,我们将实现我们的子组件并学习React生命周期相关的方法。
《React 精髓》面向初中级前端开发者,从头到尾、由浅入深地介绍了使用React 实现组件化Web 应用的完整流程。作者从React 元素、React 组件等基本的概念讲起,循序渐进地讨论了组件状态和生命周期,为开发完整的React 应用打下了基础。与第三方JavaScript 框架集成,以及对React 组件进行单元测试,都是开发React 应用的重要内容,《React 精髓》也有详细讲解。最后,为进一步提升React 应用的灵活性,作者还以实例展示了如何引入Flux 架构,让读者的开发技能更上一层楼。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。1900人阅读
React-Native(14)
我们在项目里面,经常会用的批次渲染,比如一个列表渲染很多个item,或者一个横排或者竖排同时渲染多个数据。
render1(){
var arr = [];
for(var i=0;i&5;i++){
&Text style={{fontSize:20, color: &#39;red&#39;}}&
&View style={[styles.container, styles.center]}&
{&span style=&font-family: Arial, Helvetica, sans-&&arr&/span&}
这样写,RN都会报一个警告,需要你对每个item添加一个key,在Text里添加一个属性key:
&Text key={i} style={{fontSize:20, color: &#39;red&#39;}}&
但是这个key有什么作用呢?我们在代码后面加一个console.log输出一下看看结果:
for(var i=0;i&5;i++){
console.log(arr);输出结果显示,arr所有的内容,包括那个key:
看到key和那个props了吗,我们可在未渲染之前,根据要求再次更改array里面&Text&的属性。我们现在来改一改试试看!
先看看上面代码运行的效果:
然后我们在for之后这么改看看结果如何:
for(var i=0;i&arr.i++){
if (arr[i].key == 2){
arr[i].props.style.fontSize = 40;
arr[i].props.style.color = &#39;green&#39;;
arr[i].props.children[0] = &#39;改变了哦&#39;;
结果我们可以看到,中间那个key等于2的已经改变了哦。
既然这样,那我们继续改一下,把这个arr改为这个组件的一个属性:
constructor(props){
super(props);
this.state = {
blnUpdate: false,
this.testArr = [];
for(var i=0;i&5;i++){
this.testArr.push(
&Text key={i} style={{fontSize:20, color: &#39;red&#39;}} onPress={this.changeChild.bind(this, i)}&
console.log(this.testArr);
}定义在constructor里面,并且附初始&#20540;,我们还绑定了一个按键响应, 可以看到临时变化。
另外加一个state变量,用于刷新render(为什么这么做,是RN的刷新机制,需要调用this.setState才会调用,所以弄了一个bln&#20540;,而不是把arr放在state里面
然后在绑定还按钮回调中这样做:
changeChild(key){
console.log(key);
if (this.testArr[key].props.children[0] == &#39;我变了&#39;){
this.testArr[key].props.style = {fontSize : 20, color : &#39;red&#39;};
this.testArr[key].props.children[0] = &#39;这是&#39;;
this.testArr[key].props.style = {fontSize : 30, color : &#39;green&#39;};
this.testArr[key].props.children[0] = &#39;我变了&#39;;
//这里要说说text的结构,如果text是纯文字,children就只有一个,如果中间夹杂着其他变量,react是将text分段保存的。
this.setUpdate();
setUpdate(){
this.setState({
blnUpdate: !this.state.blnUpdate
}这样我们来看效果:
点击之前:
点击之后:
哈哈,都变了哦!!不过这样做有点延时。其实改变渲染之后的东西,在学习RN之后会有一个专门的参数可供使用,就是每个组件的自带属性ref,在上面的截图我们也看到了,现在ref是null,因为没有设置,如果要使用需要这样做:
&Text key={i} ref={&#39;text&#39;+i} style={{fontSize:20, color: &#39;red&#39;}} onPress={this.changeChild.bind(this, i)}&然后使用的时候就是this.refs.text0.xxx,这个RN给我们提供一个很有用的函数,this.refs.text0.setNativeProps({style:{xxx},xxx:xxx}),这个是直接改变原生组件的属性,是不会经过render的,有时候可以提高性能。
不过这个ref有个缺点,它必须要在render之后才会产生,也就是一开始初始化后,使用this.refs.text0 会报错,这点一定要弄清楚哦!
以上这些就是今天发现key的一个用处!
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:18177次
排名:千里之外
原创:29篇
评论:37条
(1)(4)(3)(4)(4)(4)(9)966,690 三月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
深入浅出React(三):理解JSX和组件
深入浅出React(三):理解JSX和组件
日. 估计阅读时间:
欲知区块链、VR、TensorFlow等潮流技术和框架,请锁定
Author Contacted
相关厂商内容
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。一个 React Form 组件的重构思路
一个 React Form 组件的重构思路
发布于20天前
最近对团队内部 React 组件库(
)中的 Form 组件进行了重构,记录一下思考的过程。
一些前置定义:
嵌套在 Form 下面的类似 Input, Select 这样的子组件
首先我们看一下,我们的对 Form 组件的需求是什么。
获取当前变动表单的状态
校验所有必填表单是否填写完成
对外触发具体表单变化的方法 formFieldChange
暴露对外提供整个表单状态的方法
提供整个表单最新状态的方法 $Form.data
校验表单是否通过校验
对外触发 formSubmit 方法
接着我们从重构前和重构后,看如何来解决这个问题。
获取当前变动表单的状态
如何获取变动的子表单
React 父子通信需要通过 prop 传递方法,对于 Form 下面的类似与 Input 之类的子表单的变化想要通知到父级,如果不借助第三方的事件传递方法,那么就只能通过由父级通过 props 向 Input 传递 formFieldChange (假设就叫这个名字)方法,然后当子组件变化时去调用 formFieldChange 来实现。
那么问题来了,什么时候去传递这个方法呢?
不能在具体页面里面使用的时候再去每条表单里面注册这个方法,那每个用到表单组件的时候就都需要给子表单进行这样的事件绑定,这样太累了。
所以一开始,我选择通过直接递归的遍历 Form 下面的 children,只要发现这个 children 是我想要的表单类型,那么就重新克隆一个带有 formFieldChange 的组件来替换掉原来的组件。
* 获取 form 下面每一个表单对象,注入属性,并收集起来
* @param children
* @returns {*}
function getForms(children){
return React.Children.map(children, (el, i) =& {
if (!el) {
return null
switch (el.type) {
case Input:
Forms.push(el)
return React.cloneElement(
formFieldChange,
emptyInput
case Select:
Forms.push(el)
return React.cloneElement(
formFieldChange
case CheckBox:
Forms.push(el)
return React.cloneElement(
formFieldChange
if (el.props && el.props.children instanceof Array) {
const children = getForms(el.props.children)
return React.cloneElement(
这样,所有的特定子组件就都可以拿到被注册的方法。以 Input 为例,在 Input 的 onChange 方法里面去调用从父级 props 传入的 formFieldChange 就可以通知到 Form 组件了。
收集变动表单的数据。
前一步完成后,这一步就比较简单了,Input 在调用 formFieldChange 的时候把想要传递的数据作为参数传进去,在 Form 里面去对这个参数做处理,就可以拿到当前变动的表单状态数据了。
校验表单是否填写完成
前面我们收集了每一条变动表单的数据。但是要判断当前 Form 下面的表单是否填写完成,那么首先需要知道我们有多少个需要填写的表单,然后在 formFieldChange 的时候进行判断就可以了。如何来提前知道我们有多少需要填写的 Field 呢,之前我选择的是通过在使用 Form 的时候先初始化一个包含所有表单初始化状态的数据。
export default class Form ponent{
constructor(props) {
super(props)
this.Forms = []
this.formState = Object.assign({}, {
isComplete: false,
isValidate: false,
errorMsg: &#39;&#39;,
}, this.props.formState)
static propTypes = {
onChange: PropTypes.func,
onSubmit: PropTypes.func,
formState: PropTypes.object
&// 初始化一个类似这样的对象传递给 Form
formState: {
realName: {},
cityId: {},
email: {},
relativeName: {},
relativePhone: {},
companyName: {}
这样就很粗暴的解决了这个问题,但是这中间存在很多问题。
因为限定了特定的组件类型(Input,Select,CheckBox),导致不利于扩展,如果在开发过程遇到其他类型的比如自定义的子表单,那么 Form 就没法对这个自定义子表单进行数据收集,解决起来比较麻烦。
所以就在考虑另一个种实现方式, Form 只去收集一个特定条件下的组件,只要这个组件满足了这个条件,并实现了对应的接口,那么 Form 就都可以去收集处理。这样也就大大挺高了适用性。
暴露对外提供整个表单状态的方法
通过在外监听每次 Form 触发的 onChange 事件来获取整个 Form 的状态。
检验表单是否通过校验
已经有了整个 Form 的数据对象,做校验并不是什么困难。通过校验的时候调用 formSubmit 方法,没有通过校验的时候对外把错误信息添加到 Form 的 state 上去。
对外触发 formSubmit 方法
当表单通过校验的时候,对外触发 formSubmit 方法,把要提交的数据作为 formSubmit 的参数传递给外面。
前面是之前写的 Form 组件的一些思路,在实际使用中也基本能满足业务需求。
但是整个 Form 的可拓展性比较差,无法很好的接入其他自定义的组件。所以萌生了重写的想法。
对于重写的这个 Form,我的想法是:首先一定要方便使用,不需要一大堆的起始工作;其次就是可拓展性要强,除了自己已经提供的内在 Input,Select 等能够接入 Form 外,对于其他的业务中的特殊需求需要接入 Form 的时候,只要这个组件实现了特定的接口就可以了很方便的接入,而不需要大量的去修改组件内部的代码。
重构主要集中在上面需求 1 里面的内容,也就是: 获取当前变动表单的状态
获取当前表单的状态分解下来有一下几点:
获取所有需要收集的子表单 formFields
初始化 Form state
表单下面子表单数量或类型发生变化时更新 1 里面创建的 formFields
子表单内部状态发生变化时通知到父表单
获取当前变动表单的状态
获取所有需要的子表单
同样通过递归遍历 children 来获取需要收集的子表单,通过子表单的 type.name 命名规则是否符合我们的定义来决定是否要进行收集。
直接来看代码:
collectFormField = (children) =& {
const handleFieldChange = this.handleFieldChange
// 简单粗暴,在 Form 更新的时候直接清空上一次保存的 formFields,全量更新,
// 避免 formFields 内容或者数量发生变化时 this.formFields 数据不正确的问题
const FormFields = this.formFields = []
function getChildList(children){
return React.Children.map(children, (el, i) =& {
// 只要 Name 以 _Field 开头,就认为是需要 From 管理的组件
if (!el || el === null) return null
const reg = /^_Field/
const childName = el.type && el.type.name
if (reg.test(childName)) {
FormFields.push(el)
return React.cloneElement(el, {
handleFieldChange
if (el.props && el.props.children) {
const children = getChildList(el.props.children)
return React.cloneElement(el, {
只要组件的 class name 以 _Field 开头,就把它收集起来,并传入 handleFieldChange 方法,这样当一个自定义组件接入的时候,只需要在外面包一层,并把 class 的命名为以 _Field 开头的格式就可以被 Form 收集管理了。
接入组件里面需要做的就是,在合适的时机调用 handleFieldChange 方法,并把要传递的数据作为参数传递出来就可以了。
为什么一定要执迷不悟的使用遍历这种低效的方式去收集呢,其实都是为了组件上使用的方便。这样就不需要每次在引用的时候在对子表单做什么操作了。
初始化 Form state
上一步拿到了所有的子表单,然后通过调用 initialFormDataStructure 拿来初始化 Form 的 state.data 的结构,同时通知到外面 Form 发生了变化。
子表单数量或类型发生变化时
当 Form 下面子组件被添加或删除时,需要及时更新 Form Data 的结构。通过调用 updateFormDataStructure
把新增的或者修改的子表单更新到最新,并通知到外面 Form 发生了变化。
子表单内部状态发生变化时
在第一步收集子表单的时候就已经把 handleFieldChange 注入到了子表单组件里面,所以子表单来决定调用的时机。当 handleFieldChange 被调用的时候,首先对 Form state 进行更新,然后外通知子表单发生了变化,同时通知外面 Form 发生了变化。
这样看起来整个流程就走通了,但实际上存在很多问题。
首先由于 setState 是一个异步的过程,只有在 render 后才能获取到最新的 state . 这就导致,在一个生命周期循环内如果我多次调用了 setState ,那么两次调用之间对 state 的读取很可能是不准确的。(有关生命周期的详细内容可以看这篇文章:
所以我创建了一个临时变量 currentState 来存放当前状态下最新的 state ,每次 setState 的时候都对其进行更新。
另一个问题是当 Form 发生变化的时候, updateFormDataStructure 调用的过于频繁。其实只有在子表单的数量或者类型发生变化时才需要更新 Form state 的结构。而直接去对比子表单的类型是否发生变化也是意见开销很大操作,所以选择另一种折中方式。通过给 Form 当前的状态打标,将 Form 可能处于的状态都标识出来:
const&STATUS&=&{
&&Init:&&#39;Init&#39;,
&&Normal:&&#39;Normal&#39;,
&&FieldChange:&&#39;FieldChange&#39;,
&&UpdateFormDataStructure:&&#39;UpdateFormDataStructure&#39;,
&&Submit:&&#39;Submit&#39;
这样,只有在 Form 的 STATUS 处于 Normal 的时候才对其进行 updateFormDataStructure 操作。这样就可以省去很多次渲染以及无效的对外触发的 FormChange 事件。
提交和对外暴露 Form 状态的方法和之前基本一致,这样整个对 Form 的重构就算完成了,具体项目中使用体验还不错 O(&_&)O
Form 组件地址:
最后,如果看文章的你有什么更好的想法,请告诉我:stuck_out_tongue:。
查看原文:
后回复方可回复, 如果你还没有账号你可以
一个帐号。
这家伙很懒,什么都没有留下。
共收到 0 条回复
记住登录状态
还不是会员
快速登录:

我要回帖

更多关于 京东退款邮费多久到账 的文章

 

随机推荐