MUI-Datatable、child 组件检测 parent 组件的状态变化
MUI-Datatable, child component detecting change in the state of the parent component
我刚开始使用 React,这个问题让我睡不着觉。
据我了解,状态 (this.state) 只能在创建它的组件中访问,但在我的项目中这不会发生。 child 组件能够识别对 parent 组件的状态所做的更改,这减慢了我的应用程序,让我尝试用代码更好地解释它:
在 parent 组件中,我渲染了页面标题,一个用于添加新记录的模式,并包含了 child 负责组装数据的组件table(由 MUI-Datatable 和通过 API 加载的数据):
https://github.com/gregnb/mui-datatables
Parent分量:
import React from 'react';
import Typography from '@material-ui/core/Typography';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
// Components
import CadcliFieldContent from './CadcliFieldContent';
class CadcliField extends React.Component {
state = {
isLoading: false,
isNew: true,
dialog: false,
formData: {
name: 'Test'
}
};
openDialog = () => { this.setState({ dialog: true }); }
closeDialog = () => { this.setState({ dialog: false }); }
formDataChange = name => event => {
var formDataTemp = this.state.formData;
formDataTemp[name] = event.target.value;
this.setState({ formData: formDataTemp });
};
render () {
return (
<div className="flex flex-1 w-full">
<div className="flex items-center justify-end">
<Button
className="normal-case"
variant="contained"
color="secondary"
role="button"
onClick={this.openDialog}
>
<Icon>add</Icon>
<span className="mx-1">{t('NEW_FIELD')}</span>
</Button>
</div>
<CadcliFieldContent
urlBase="cadcli-field/"
/>
<Dialog
className='max-w-lg w-full m-24'
onClose={this.closeDialog}
open={this.state.dialog}
>
<DialogTitle component="div" className="p-0">
<Typography className="text-16 ml-8">Fields</Typography>
</DialogTitle>
<DialogContent className="p-16 sm:p-24">
<div className="flex items-center mb-24">
<TextField
type="text"
name="name"
id="input-name"
value={formData.name}
onChange={this.formDataChange('name')}
variant="outlined"
fullWidth
required
/>
</div>
</DialogContent>
</Dialog>
</div>
);
};
}
export default CadcliField;
Child分量
import React from 'react';
import axios from 'axios';
import MUIDataTable from "mui-datatables";
import { CircularProgress, Typography } from '@material-ui/core';
class CadcliContent extends React.Component {
state = {
urlApi: process.env.REACT_APP_API_URL,
page: 0,
count: 1,
rowsPerPage: 10,
denseTable: true,
idDeleteModal: 0,
sortOrder: {},
filter: {},
search: '',
data: [['Loading...']],
columns: [
{name: 'id', label: this.props.t('utils:ACTIONS')},
{name: 'id', label: 'ID', options: { filterType : 'textField' }},
],
isLoading: false,
};
componentDidMount() {
this.getData(this.state.urlApi, 0);
}
// get data
getData = async (url, page) => {
this.setState({ isLoading: true });
const res = await this.axiosRequest(url, page);
this.setState({ data: res.data, isLoading: false, count: res.total });
};
axiosRequest = (url, page, sortOrder = {}, filter = {}, searchText = '') => {
url = url + '?page_size=' + this.state.rowsPerPage;
url += '&page=' + (page + 1);
// Ordering
if (sortOrder.name !== undefined) {
if (sortOrder.direction === 'asc') {
url += '&ordering=' + sortOrder.name;
} else {
url += '&ordering=-' + sortOrder.name;
}
}
// Filtering
if (filter.length > 0) {
var columnsTable = this.state.columns;
filter.forEach(function(value, key){
if (value.length > 0) {
url += '&' + columnsTable[key].name + '=' + value;
}
});
}
// Generic Searching
if (searchText !== "" && searchText !== null) {
url += '&search=' + searchText;
}
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
resolve({
data: response.data.results,
page: page,
total: response.data.count,
});
}).catch(function (error) {
resolve({
data: [],
page: 0,
total: 0,
});
});
});
};
sort = (page, sortOrder, filter) => {
this.setState({ isLoading: true });
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
data: res.data,
page: res.page,
sortOrder,
filter,
isLoading: false,
count: res.total,
});
});
};
changePage = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
filterChange = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
searchTable = (page, sortOrder, columnsFilter, searchText) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, columnsFilter, searchText).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter: columnsFilter,
search: searchText,
data: res.data,
count: res.total,
});
});
}
render() {
const { data, count, isLoading, rowsPerPage, sortOrder } = this.state;
const options = {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
serverSide: true,
download: false,
count: count,
rowsPerPage: rowsPerPage,
rowsPerPageOptions: [],
sortOrder: sortOrder,
resizableColumns: false,
confirmFilters: true,
onTableChange: (action, tableState) => {
switch (action) {
case 'changePage':
this.changePage(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'sort':
this.sort(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'search':
this.searchTable(tableState.page, tableState.sortOrder, tableState.filterList, tableState.searchText);
break;
default:
console.log('[' + action + '] action not handled.');
}
},
setTableProps: () => {
return {
size: this.state.denseTable ? 'small' : 'medium'
};
},
};
return (
<div className="w-full flex flex-col">
<MUIDataTable
title={
<Typography variant="h6">
Listing
{isLoading && <CircularProgress size={24} />}
</Typography>
}
data={data}
columns={this.state.columns}
options={options}
/>
</div>
);
}
}
export default CadcliContent;
发生了什么:
每次我在parent组件中改变状态,例如在TextField中输入并改变this.state.formData.name属性,或者点击按钮打开MUI Datatables 的模式(更改 this.state.dialog)的 onTableChange 函数被触发并在我的 table 中生成一个处理,我意识到这一点是因为我放了一个 console.log() 通知此:
onTableChange: (action, tableState) => {
switch (action) {
/* .... */
default:
console.log('[' + action + '] action not handled.');
}
},
(摘自触发console.log的代码)
这让我感到不安,因为 MUI-Datatable 明白它在 this.state 中改变了一些东西,并做了一个非常繁重的处理,甚至在延迟输入中留下了打字的处理。
知道会发生什么吗?
解决方案比我想象的要简单,我最终通过学习和理解 React state 概念解决了它。
尽管我仍然不明白 MUI-Datables 组件如何观察整个状态,但将 Dialog 封装在一个新组件中并具有新的隔离状态这一简单事实,奇迹发生了。
所以如果有人遇到同样的问题,解决方案是这样的:
CadcliField.js
<CadcliFieldContent
urlBase="cadcli-field/"
{...this.props}
/>
<CadcliFieldFormNew
isNew={this.state.isNew}
dialog={this.state.dialog}
closeDialog={this.closeDialog}
/>
我刚开始使用 React,这个问题让我睡不着觉。
据我了解,状态 (this.state) 只能在创建它的组件中访问,但在我的项目中这不会发生。 child 组件能够识别对 parent 组件的状态所做的更改,这减慢了我的应用程序,让我尝试用代码更好地解释它:
在 parent 组件中,我渲染了页面标题,一个用于添加新记录的模式,并包含了 child 负责组装数据的组件table(由 MUI-Datatable 和通过 API 加载的数据):
https://github.com/gregnb/mui-datatables
Parent分量:
import React from 'react';
import Typography from '@material-ui/core/Typography';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
// Components
import CadcliFieldContent from './CadcliFieldContent';
class CadcliField extends React.Component {
state = {
isLoading: false,
isNew: true,
dialog: false,
formData: {
name: 'Test'
}
};
openDialog = () => { this.setState({ dialog: true }); }
closeDialog = () => { this.setState({ dialog: false }); }
formDataChange = name => event => {
var formDataTemp = this.state.formData;
formDataTemp[name] = event.target.value;
this.setState({ formData: formDataTemp });
};
render () {
return (
<div className="flex flex-1 w-full">
<div className="flex items-center justify-end">
<Button
className="normal-case"
variant="contained"
color="secondary"
role="button"
onClick={this.openDialog}
>
<Icon>add</Icon>
<span className="mx-1">{t('NEW_FIELD')}</span>
</Button>
</div>
<CadcliFieldContent
urlBase="cadcli-field/"
/>
<Dialog
className='max-w-lg w-full m-24'
onClose={this.closeDialog}
open={this.state.dialog}
>
<DialogTitle component="div" className="p-0">
<Typography className="text-16 ml-8">Fields</Typography>
</DialogTitle>
<DialogContent className="p-16 sm:p-24">
<div className="flex items-center mb-24">
<TextField
type="text"
name="name"
id="input-name"
value={formData.name}
onChange={this.formDataChange('name')}
variant="outlined"
fullWidth
required
/>
</div>
</DialogContent>
</Dialog>
</div>
);
};
}
export default CadcliField;
Child分量
import React from 'react';
import axios from 'axios';
import MUIDataTable from "mui-datatables";
import { CircularProgress, Typography } from '@material-ui/core';
class CadcliContent extends React.Component {
state = {
urlApi: process.env.REACT_APP_API_URL,
page: 0,
count: 1,
rowsPerPage: 10,
denseTable: true,
idDeleteModal: 0,
sortOrder: {},
filter: {},
search: '',
data: [['Loading...']],
columns: [
{name: 'id', label: this.props.t('utils:ACTIONS')},
{name: 'id', label: 'ID', options: { filterType : 'textField' }},
],
isLoading: false,
};
componentDidMount() {
this.getData(this.state.urlApi, 0);
}
// get data
getData = async (url, page) => {
this.setState({ isLoading: true });
const res = await this.axiosRequest(url, page);
this.setState({ data: res.data, isLoading: false, count: res.total });
};
axiosRequest = (url, page, sortOrder = {}, filter = {}, searchText = '') => {
url = url + '?page_size=' + this.state.rowsPerPage;
url += '&page=' + (page + 1);
// Ordering
if (sortOrder.name !== undefined) {
if (sortOrder.direction === 'asc') {
url += '&ordering=' + sortOrder.name;
} else {
url += '&ordering=-' + sortOrder.name;
}
}
// Filtering
if (filter.length > 0) {
var columnsTable = this.state.columns;
filter.forEach(function(value, key){
if (value.length > 0) {
url += '&' + columnsTable[key].name + '=' + value;
}
});
}
// Generic Searching
if (searchText !== "" && searchText !== null) {
url += '&search=' + searchText;
}
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
resolve({
data: response.data.results,
page: page,
total: response.data.count,
});
}).catch(function (error) {
resolve({
data: [],
page: 0,
total: 0,
});
});
});
};
sort = (page, sortOrder, filter) => {
this.setState({ isLoading: true });
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
data: res.data,
page: res.page,
sortOrder,
filter,
isLoading: false,
count: res.total,
});
});
};
changePage = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
filterChange = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
searchTable = (page, sortOrder, columnsFilter, searchText) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, columnsFilter, searchText).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter: columnsFilter,
search: searchText,
data: res.data,
count: res.total,
});
});
}
render() {
const { data, count, isLoading, rowsPerPage, sortOrder } = this.state;
const options = {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
serverSide: true,
download: false,
count: count,
rowsPerPage: rowsPerPage,
rowsPerPageOptions: [],
sortOrder: sortOrder,
resizableColumns: false,
confirmFilters: true,
onTableChange: (action, tableState) => {
switch (action) {
case 'changePage':
this.changePage(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'sort':
this.sort(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'search':
this.searchTable(tableState.page, tableState.sortOrder, tableState.filterList, tableState.searchText);
break;
default:
console.log('[' + action + '] action not handled.');
}
},
setTableProps: () => {
return {
size: this.state.denseTable ? 'small' : 'medium'
};
},
};
return (
<div className="w-full flex flex-col">
<MUIDataTable
title={
<Typography variant="h6">
Listing
{isLoading && <CircularProgress size={24} />}
</Typography>
}
data={data}
columns={this.state.columns}
options={options}
/>
</div>
);
}
}
export default CadcliContent;
发生了什么:
每次我在parent组件中改变状态,例如在TextField中输入并改变this.state.formData.name属性,或者点击按钮打开MUI Datatables 的模式(更改 this.state.dialog)的 onTableChange 函数被触发并在我的 table 中生成一个处理,我意识到这一点是因为我放了一个 console.log() 通知此:
onTableChange: (action, tableState) => {
switch (action) {
/* .... */
default:
console.log('[' + action + '] action not handled.');
}
},
(摘自触发console.log的代码)
这让我感到不安,因为 MUI-Datatable 明白它在 this.state 中改变了一些东西,并做了一个非常繁重的处理,甚至在延迟输入中留下了打字的处理。
知道会发生什么吗?
解决方案比我想象的要简单,我最终通过学习和理解 React state 概念解决了它。
尽管我仍然不明白 MUI-Datables 组件如何观察整个状态,但将 Dialog 封装在一个新组件中并具有新的隔离状态这一简单事实,奇迹发生了。
所以如果有人遇到同样的问题,解决方案是这样的:
CadcliField.js
<CadcliFieldContent
urlBase="cadcli-field/"
{...this.props}
/>
<CadcliFieldFormNew
isNew={this.state.isNew}
dialog={this.state.dialog}
closeDialog={this.closeDialog}
/>