在react中,模块组件化了之后,最常使用的就是两个组件之间相互传递数据,其中就包括父组件向子组件传递数据和子组件向父组件传递数据两种情景,下面通过前几天写的一个上传图片时截图的组件为例子,演示组件之间的数据传递。
//父组件LabelManageEdit.js代码片
<CropperUpload
pattern={1}//模式分为三种,1为只能上传一张图片,2为传一张图片,并且可以预览,3为传多张图片
width={375}
height={120}
onChange={this.ChangeIconUrl.bind(this)}
fileList={this.state.iconUrl}
/>
子组件接收参数时,在constructor构造函数中对父组件传递过来的参数进行绑定,调用super(props)来获取props,再通过props.指定的父组件传过来的参数来绑定给子组件的state上。如下为子组接收父组件参数代码。
//子组件CropperUpload.js代码片
constructor(props) {
super(props);
this.state = {
width: props.width,
height: props.height,
pattern: props.pattern,
fileList: props.fileList ? props.fileList : [],
editImageModalVisible: false,
srcCropper: '',
selectImgName: '',
}
}
constructor构造函数只会在首次创建子组件的实例的时候调用一次,后来页面在进行重新的渲染的时候将不会在调用该构造函数,所以这种方式不适用于当父组件传递过来得参数是变化的情况,在该例子中,父组件向子组件传递参数fileList是从后台异步获取来的。因此当通过fileList={this.state.fileList}向子组件传递该图片列表参数时,起初是为空数组,在后来请求数据回来重新加载页面的时候才会或得到该数据。
//父组件LabelManageEdit.js代码片
componentWillMount() {
const { dispatch } = this.props;
if (this.props.location.params) {
this.setState({
labelName: this.props.location.params.record.productName,
status: this.props.location.params.record.isActive,
});
if(this.props.location.params.record.iconUrl){
let iconUrl=[];
iconUrl.push({
uid: -1,
name: 'iconUrl.png',
status: 'done',
url:this.props.location.params.record.iconUrl,
});
this.setState({
iconUrl,
});
}
}
}
当是以上情况的时候,子组件在绑定父组件参数的时候就需要用componentWillReceiveProps函数,当props发生变化时执行,初始化render时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用。
//子组件CropperUpload.js代码片
componentWillReceiveProps(nextProps) {
if ('fileList' in nextProps) {
this.setState({
fileList: nextProps.fileList ? nextProps.fileList : [],
});
}
}
以上就是父组件向子组件传递参数的方法。
2. 子组件向父组件传递参数
在这里主要是子组件进行图片裁剪或者图片删除的时候,fileList发生变化,希望将这种变化同步到父组件中,同时更新父组件的state里面的fileList,最开始的时候尝试用ref来获取组件的实时state的状态。采用ref=“cropperUpload”也就是字符串的方式来挂载组件实例。最后取出实例中的state。打印结果。发现不能取出来子组件的state,只能获取到子组件的props。查阅资料说这种通过ref="字符串"的实例化方式已经废弃掉。
因此就想了另一种回调函数的方法,当子组件的状态发生变化,触发回调函数,回调函数将子组件的state传递给父组件实现更新的目的。这里将父组件的ChangeIconURL函数传递给子组件,并绑定到子组件的onChange函数上。
//父组件LabelManageEdit.js代码片
<CropperUpload
pattern={1}//模式分为三种,1为只能上传一张图片,2为传一张图片,并且可以预览,3为传多张图片
width={375}
height={120}
onChange={this.ChangeIconUrl.bind(this)}
fileList={this.state.iconUrl}
/>
在子组件中,fileList发生变化有两处。上传图片的回调,和删除图片的回调都会改变子组件fileList的状态,因此在这两个函数的最后,调用这个onChange函数,将更新后的fileList传递给父组件。以下为子组件中删除图片回调。调用了由父组件传递进来的回调函数this.prop.onChange(fileList);
//子组件CropperUpload.js代码片
handleRemove(file) {
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
this.props.onChange(newFileList);
return {
fileList: newFileList,
};
});
}
父组件在接收到这个fileList之后,更新父组件的state中的fileList。进而实现子组件向父组件传递参数
//父组件LabelManageEdit.js代码片
//标签内容图片的回调函数
ChangeIconUrl = (value) => {
this.setState({
iconUrl: value,
})
}
import React, { PureComponent } from 'react';
import { routerRedux, Route, Switch } from 'dva/router';
import { Upload,Spin, Card, Button, Form, Icon, Col, Row, DatePicker, TimePicker, Input, Select, Popover } from 'antd';
import { connect } from 'dva';
import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
import PartModal from '../../../components/Part/partModal';
import { message } from 'antd';
import stylesDisabled from './AddPart.less';
const { Option } = Select;
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
import CropperUpload from '../../../components/Upload/CropperUpload';
const fieldLabels = {
name: '标签名称',
isActive: '状态',
};
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 5 },
},
};
const submitFormLayout = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 10, offset: 7 },
},
};
@connect(state => ({
changeData: state.modifyDataCQX.changeData,
submitLoading:state.modifyDataCQX.loading,
}))
@Form.create()
export default class LabelManageNew extends PureComponent {
constructor(props) {
super(props);
this.state = {
labelName: "",
status: "",
iconUrl:[],
}
}
componentWillMount() {
const { dispatch } = this.props;
if (this.props.location.params) {
this.setState({
labelName: this.props.location.params.record.productName,
status: this.props.location.params.record.isActive,
});
if(this.props.location.params.record.iconUrl){
let iconUrl=[];
iconUrl.push({
uid: -1,
name: 'iconUrl.png',
status: 'done',
url:this.props.location.params.record.iconUrl,
});
this.setState({
iconUrl,
});
}
}
}
returnClick = () => {
this.props.history.goBack();
}
validate = () => {
const { form, dispatch, submitting } = this.props;
const { getFieldDecorator, validateFieldsAndScroll, getFieldsError } = form;
validateFieldsAndScroll((error, values) => {
if (!error) {
let iconUrl='';
if (this.state.iconUrl.length) {
iconUrl=this.state.iconUrl[0].url;
}
else{
message.error("请添加分区图标");
return;
}
values.iconUrl=iconUrl;
var data={
ID:this.props.location.params.record.id,
operatorName:this.props.location.params.record.operatorName,
labelName:values.labelName,
status:values.status,
iconURL:values.iconUrl,
}
dispatch({
type: 'modifyDataCQX/editLabelManagementFunc',
payload: data,
callback: () => {
const { changeData } = this.props;
if (changeData.success === true) {
message.success(changeData.msg);
this.props.history.goBack();
} else {
message.error(changeData.msg);
}
},
});
}
});
}
//标签内容图片的回调函数
ChangeIconUrl = (value) => {
this.setState({
iconUrl: value,
})
}
render() {
const { form, dispatch } = this.props;
const { submitLoading } = this.props;
const { getFieldDecorator, validateFieldsAndScroll, getFieldsError } = form;
return (
<PageHeaderLayout >
<Card bordered={false} title="标签信息">
<Form hideRequiredMark className={stylesDisabled.disabled}>
<FormItem {...formItemLayout} label={fieldLabels.name} >
{getFieldDecorator('labelName', {
rules: [{ required: true, message: '请输入标签名称' }],
initialValue: this.state.labelName,
})
(<Input placeholder="请输入标签名称" />)
}
</FormItem>
<FormItem {...formItemLayout} label={fieldLabels.isActive} >
{getFieldDecorator('status', {
rules: [{ required: true, message: '请输入状态' }],
initialValue: this.state.status,
})
(<Select style={{ width: 100 }}>
<Option value={true}>有效</Option>
<Option value={false}>无效</Option>
</Select>)
}
</FormItem>
<FormItem {...formItemLayout} label='标签图标'>
<CropperUpload
pattern={1}//模式分为三种,1为只能上传一张图片,2为传一张图片,并且可以预览,3为传多张图片
width={375}
height={120}
onChange={this.ChangeIconUrl.bind(this)}
fileList={this.state.iconUrl}
/>
</FormItem>
<FormItem {...submitFormLayout} style={{ marginTop: 32 }}>
<Button type="primary" onClick={this.validate} loading={submitLoading}>
提交 </Button >
< Button style={{ marginLeft: 8 }} onClick={this.returnClick} >
返回
</Button >
</FormItem>
</Form>
</Card>
</PageHeaderLayout>
);
}
}
子组件CropperUpload.js代码
import React, { PureComponent } from 'react';
import moment from 'moment';
import { routerRedux, Route, Switch, Link } from 'dva/router';
import { Upload, Button, Modal, Icon, message } from 'antd';
import "cropperjs/dist/cropper.css"
import Cropper from 'react-cropper'
import { connect } from 'dva';
import styles from './Upload.less';
@connect(state => ({
imagePictures: state.getData.imagePictures,
}))
class CropperUpload extends PureComponent {
constructor(props) {
super(props);
this.state = {
width: props.width,
height: props.height,
pattern: props.pattern,
fileList: props.fileList ? props.fileList : [],
editImageModalVisible: false,
srcCropper: '',
selectImgName: '',
}
}
componentWillReceiveProps(nextProps) {
if ('fileList' in nextProps) {
this.setState({
fileList: nextProps.fileList ? nextProps.fileList : [],
});
}
}
handleCancel = () => {
this.setState({
editImageModalVisible: false,
});
}
// 特产介绍图片Upload上传之前函数
beforeUpload(file, fileList) {
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) { //添加文件限制
MsgBox.error({ content: '文件大小不能超过10M' });
return false;
}
var reader = new FileReader();
const image = new Image();
var height;
var width;
//因为读取文件需要时间,所以要在回调函数中使用读取的结果
reader.readAsDataURL(file); //开始读取文件
reader.onload = (e) => {
image.src = reader.result;
image.onload = () => {
height = image.naturalHeight;
width = image.naturalWidth;
if (height < this.state.height || width < this.state.width) {
message.error('图片尺寸不对 宽应大于:'+this.state.width+ '高应大于:' +this.state.height );
this.setState({
editImageModalVisible: false, //打开控制裁剪弹窗的变量,为true即弹窗
})
}
else{
this.setState({
srcCropper: e.target.result, //cropper的图片路径
selectImgName: file.name, //文件名称
selectImgSize: (file.size / 1024 / 1024), //文件大小
selectImgSuffix: file.type.split("/")[1], //文件类型
editImageModalVisible: true, //打开控制裁剪弹窗的变量,为true即弹窗
})
if (this.refs.cropper) {
this.refs.cropper.replace(e.target.result);
}
}
}
}
return false;
}
handleRemove(file) {
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
this.props.onChange(newFileList);
return {
fileList: newFileList,
};
});
}
//将base64码转化成blob格式
convertBase64UrlToBlob(base64Data) {
var byteString;
if (base64Data.split(',')[0].indexOf('base64') >= 0) {
byteString = atob(base64Data.split(',')[1]);
} else {
byteString = unescape(base64Data.split(',')[1]);
}
var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: this.state.selectImgSuffix });
}
//将base64码转化为FILE格式
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
_ready() {
this.refs.cropper.setData({
width: this.state.width,
height: this.state.height,
});
}
saveImg() {
const { dispatch } = this.props;
var formdata = new FormData();
formdata.append("files", this.dataURLtoFile(this.refs.cropper.getCroppedCanvas().toDataURL(), this.state.selectImgName));
dispatch({
type: 'getData/postimage',
payload: formdata,
callback: () => {
const { success, msg, obj } = this.props.imagePictures;
if (success) {
let imageArry = this.state.pattern == 3 ? this.state.fileList.slice() : [];
imageArry.push({
uid: Math.random() * 100000,
name: this.state.selectImgName,
status: 'done',
url: obj[0].sourcePath,
// url:obj[0].sourcePath,
thumbUrl: this.refs.cropper.getCroppedCanvas().toDataURL(),
thumbnailPath: obj[0].thumbnailPath,
largePath: obj[0].largePath,
mediumPath: obj[0].mediumPath,
upload: true,
})
this.setState({
fileList: imageArry,
editImageModalVisible: false, //打开控制裁剪弹窗的变量,为true即弹窗
})
this.props.onChange(imageArry);
}
else {
message.error(msg);
}
},
});
}
render() {
const botton = this.state.pattern == 2 ?
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div> :
<Button>
<Icon type="upload" />选择上传</Button>
return (
<div>
<Upload
name="files"
action="/hyapi/resource/image/multisize/upload"
listType={this.state.pattern === 1 ? " " : this.state.pattern === 2 ? "picture-card" : "picture"}
className={this.state.pattern === 3 ? styles.uploadinline : ""}
beforeUpload={this.beforeUpload.bind(this)}
onRemove={this.handleRemove.bind(this)}
fileList={this.state.fileList}
>
{botton}
</Upload>
<Modal
key="cropper_img_icon_key"
visible={this.state.editImageModalVisible}
width="100%"
footer={[
<Button type="primary" onClick={this.saveImg.bind(this)} >保存</Button>,
<Button onClick={this.handleCancel.bind(this)} >取消</Button>
]}>
<Cropper
src={this.state.srcCropper} //图片路径,即是base64的值,在Upload上传的时候获取到的
ref="cropper"
preview=".uploadCrop"
viewMode={1} //定义cropper的视图模式
zoomable={true} //是否允许放大图像
movable={true}
guides={true} //显示在裁剪框上方的虚线
background={false} //是否显示背景的马赛克
rotatable={false} //是否旋转
style={{ height: '100%', width: '100%' }}
cropBoxResizable={false}
cropBoxMovable={true}
dragMode="move"
center={true}
ready={this._ready.bind(this)}
/>
</Modal>
</div>
);
}
}
export default CropperUpload;