React可以说是当前最火的前端框架了(官方低调的说是JS类库),相对于angularjs来说轻量不少,这也导致了,我们在处理具体的业务时会碰到很多React解决不了的东西,这时就需要第三方类库去处理。而Antd作为对React支持最好的页面渲染技术,自然而然的在React项目中广泛应用。
最近在研究React+Antd的框架搭建项目时,碰到了一些问题。之前使用Bootstrap+angularjs时,习惯了Bootstrap的那种方式,再使用到Antd时有点不适应。好在官方都有文档。
我们知道在单页面应用中,最常用的外层嵌套就应该是Panle(面板)了,我们可以在Panle中加入不同的东西,比如,table,form,报表等。但是遗憾的是,我没有再Antd中发现Panel组件,更多的是折叠面板,不符合我的需求。
在使用到Bootstrap时,一个基本的Panle包含Header,Body,Footer三部分。
Header是提示栏,title
Body应该是我的Form表单
Footer应该包含一些自定义操作按钮。
但在Antd中没有发现类似的组件,先考虑引入bootstrap,但是一想,antd中有很多组件都类似bootstrap,不能因为一个panel组件而引入整个bootstrap,而且bootstrap本身还有依赖于jquery,想了想不划算。
于是自己合计去封装一个Panle,正好对应着React一切皆组件的思想。
话不多说,来说一下思路。
首先,对于Header,我们来看看哪些东西需要我们封装。Header最主要的应该包含两部分,一个是title,一个是背景色(当然你也可以定义其他的属性,比如字体等,这里用title和背景色做用例)。
因为我们的不同模块对应着不同的title,那么这个title就需要作为参数传递过来。学过React的都知道,从父组件向子组件传递的属性,我们都可以在子组件中通过props属性来获取。
那么现在我们可以确定的是 props中至少包含{title:"",background:""}。
那Header就可以这么定义:
<div style={{ width: "100%", background: "#f5f5f5",lineHeight:"40px" }}>
<span style={{paddingLeft:"20px"}}>{this.props.title}</span>
</div>
其次Body体中我们怎么将父组件中的DOM元素Form表单传递给子组件。
当然在Bootstrap中很简单
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">面板标题</h3>
</div>
<div class="panel-body">
这是一个基本的面板
</div>
<div class="panel-footer">
这里是Footer
</div>
</div>
我们直接早panel-body中加入我们的form表单就行,但是使用React就不一样了,首相我想到的就是怎么将DOM元素加载到子组件中,是不是也是通过props的属性传递的。
下面我们来看一下props的值
这里注意一下children这个属性,他的类型是个react.element那它是不是就代表着DOM中的元素。
于是关于Body中的定义我们就可以这么定义:
<div style={{ width: "100%", height: "90%", textAlign: "center"}}>
<div style={{ width: "100%", height: "90%", textAlign: "center", marginTop: "20px" }}>
{this.props.children}
</div>
</div>
然后,关于Footer的定义,其实我们在Footer主要需要两个按钮,一个保存,一个取消。一个向后台发起请求,一个取消表单然后返回或者做其他操作。
在React中怎么给在子组件中触发父组件的方法呢?
React给出的解决方案有好几个,可以通过父组件传递一个callback函数,让子组件触发父组件的函数,就可以了。另外一种就是通过上下文(这里不介绍这种方式)。
原理我们现在清除了,就可以定义Footer部分了:
<div style={{ width: "100%", height: "50px", background: "#f5f5f5" }}>
<div style={{ float: "right" , lineHeight: "50px"}}>
<span style={{ paddingRight: "10px" }}><Button type="primary" onClick={this.props.panelConfig.cancelFunction}>{this.props.panelConfig.cancel ? this.props.panelConfig.cancel : "Cancel"}</Button></span>
<span style={{ paddingRight: "10px" }}><Button type="primary" onClick={this.props.panelConfig.finishFunction}>{this.props.panelConfig.finish ? this.props.panelConfig.finish : "Save"}</Button></span>
</div>
</div>
父组件中我们改善一下,将要传递的属性值都封装到一个对象中,然后通过props传递给子组件
const panel = {
title: "Create NAS Client",
size: "middle",
cancelLable: "Cancel",
finishLable: "Save",
finishFunction: this.createNasClient,
cancelFunction: this.returnList
}
父组件render函数中:
return (
<div style={{ width: "100%", paddingTop: '50px' }}>
<CustomPanle panelConfig={panel}>
<Form {...formItemLayout}>
<Form.Item label="Nas Name">
{getFieldDecorator('nasName', {
rules: [
{
required: true,
message: 'Please input your E-mail!',
},
],
})(<Input />)}
</Form.Item>
<Form.Item label="Nas Start IP" hasFeedback >
{getFieldDecorator('nasStartIp', {
rules: [
{
required: true,
message: 'Please input your password!',
},
{
validator: this.validateToNextPassword,
},
],
})(<Input />)}
</Form.Item>
<Form.Item label="Nas End IP" hasFeedback >
{getFieldDecorator('nasEndIp', {
rules: [
{
required: true,
message: 'Please confirm your password!',
},
{
validator: this.compareToFirstPassword,
},
],
})(<Input onBlur={this.handleConfirmBlur} />)}
</Form.Item>
<Form.Item
label={
<span>
Shared Secret
</span>
}
>
{getFieldDecorator('shareSecurity', {
rules: [{ required: true, message: 'Please input your discription!', whitespace: true }],
})(<TextArea />)}
</Form.Item>
<Form.Item label="DM-Attribute" hasFeedback >
{getFieldDecorator('dmAttribute', {
rules: [
{
required: true,
message: 'Please confirm your password!',
},
{
validator: this.compareToFirstPassword,
},
],
})(<Select
mode="multiple"
placeholder="Please select"
defaultValue={[]}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{children}
</Select>)}
</Form.Item>
<Form.Item label="COA-Attribute" hasFeedback >
{getFieldDecorator('coaAttribute', {
rules: [
{
required: true,
message: 'Please confirm your password!',
},
{
validator: this.compareToFirstPassword,
},
],
})(<Select
mode="multiple"
placeholder="Please select"
defaultValue={[]}
onChange={this.handleChange}
style={{ width: '100%' }}
>
{children}
</Select>)}
</Form.Item>
</Form>
</CustomPanle>
</div >
);
}
那我们在子组件中就可以通过this.props.panelConfig来获取对应的属性,然后绑定到Dom上:
return (
<div style={{ width: "100%", height: this.state.height, border: "1px solid #ddd" }}>
<div style={{ width: "100%", background: "#f5f5f5",lineHeight:"40px" }}>
<span style={{paddingLeft:"20px"}}>{this.props.panelConfig.title}</span>
</div>
<div style={{ width: "100%", height: "90%", textAlign: "center"}}>
<div style={{ width: "100%", height: "90%", textAlign: "center", marginTop: "20px" }}>
{this.props.children}
</div>
</div>
<div style={{ width: "100%", height: "50px", background: "#f5f5f5" }}>
<div style={{ float: "right" , lineHeight: "50px"}}>
<span style={{ paddingRight: "10px" }}><Button type="primary" onClick={this.props.panelConfig.cancelFunction}>{this.props.panelConfig.cancel ? this.props.panelConfig.cancel : "Cancel"}</Button></span>
<span style={{ paddingRight: "10px" }}><Button type="primary" onClick={this.props.panelConfig.finishFunction}>{this.props.panelConfig.finish ? this.props.panelConfig.finish : "Save"}</Button></span>
</div>
</div>
</div>
);
这样就可以达到不同组建中Panel共享的目的。
这里也可以扩展其他的属性,比如根据不同的需求调整panel的大小,可以传递一个size的属性,然后子组件根据size的值来设置Panel的大小。当然还有背景色。
看一下有没有执行父类的方法
首先在父组件中定义cancel和save的方法:
createNasClient = () => {
console.log("创建----");
// history.push("/index/policy-engine/nas-client")
}
returnList = () => {
console.log("取消----");
// history.push("/index/policy-engine/nas-client")
}
点击save和cancel,查看依稀爱控制台:
说明已经成功触发了父组件的方法,那么当我们点击的时候就可以做一些操作。
看一下最终效果: