在上传前读取文件内容时反应渲染无限循环
React render infinite loop when reading file content before upload
当使用 FileReader API 选择要上传的文件时,我正在尝试在 React/Redux 应用程序中显示一些文件内容。我能够显示内容,但在 FileReader 的 onload 事件处理程序中调用 setState 导致发生无限渲染。
import _ from 'lodash';
import React, { Component } from 'react';
import { reduxForm, Field, formValueSelector } from 'redux-form';
import Button from 'material-ui/Button';
import * as actions from '../../actions';
import { connect } from 'react-redux';
import {
Select,
TextField,
} from 'redux-form-material-ui';
import { renderFileInput } from '../helpers/form_helpers';
class ImportLeads extends Component {
state = {
fields: []
}
handleFormSubmit({ leadsCSV }) {
const { listid } = this.props;
this.props.importLeads(leadsCSV, listid);
}
renderMapping() {
const { CSVFile } = this.props;
console.log(CSVFile);
const temp = [];
if(CSVFile) {
const r = new FileReader();
r.readAsText(CSVFile, "UTF-8");
r.onload = (e) => {
const content = e.target.result;
const firstLine = content.split('\n', 1)[0];
const fieldsArray = firstLine.split(',');
console.log(fieldsArray);
_.map(fieldsArray, field => {
console.log(field);
temp.push(<div>{field}</div>);
});
this.setState({ fields: temp });
}
r.onerror = function(e) {
console.log("Error reading file");
}
}
return (
<div>
{this.state.fields}
</div>
);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<div>
<Field
name="leadsCSV"
component={renderFileInput}
type="file"
text="Import CSV"
/>
<div style={{ marginTop: '10px' }}>
<Button type="submit" variant="raised">Upload</Button>
</div>
</div>
</form>
{this.renderMapping()}
</div>
);
}
}
ImportLeads = reduxForm({
form: 'importLeads',
})(ImportLeads);
const selector = formValueSelector('importLeads');
ImportLeads = connect(
state => {
const CSVFile = selector(state, 'leadsCSV');
return {
CSVFile
}
}
)(ImportLeads);
export default ImportLeads;
我使用 redux 形式处理文件输入,并通过使用 connect 使其作为 props 可用来获取文件的值。
我读到,在 render 方法内部调用 setState 时,通常会发生无限渲染。有没有更好的方法来解决这个问题而不让它无限渲染?谢谢
在 renderMapping()
内部,您调用 setState
导致 re-render 组件。
在组件 render
中,您 {this.renderMapping()}
导致再次调用 renderMapping
。
此过程重复 n 次导致无限渲染。
如果 fieldsArray
已经包含 field
,您应该签入 renderMapping
。如果是这样,你不应该把它推到数组中。 (因为你正在将另一个 div 中的相同字段推入你的数组,它不断导致 re-render)
if (fieldsArray.includes(field)) {
return;
}
React 有特殊的 "lifecycle" 方法,对这个用例非常有用。
如果您定义 componentWillMount()
方法,它将在第一次渲染之前被调用。如果您有额外的资源需要加载,您可以从那里开始。
因为看起来你想要 re-run 那些计算,如果道具改变你也会想要使用 componentWillReceiveProps(nextProps)
到 re-run 你的计算基于新的道具。请注意,this.props
尚未反映新道具,您需要从参数中获取这些道具。
最后,虽然对你的情况没有必要,但你应该熟悉 componentWillUnmount()
,如果你没有这样做,你可以自己清理(例如删除事件侦听器、取消计时器等)很容易导致内存泄漏甚至更糟。
您可以在文档中熟悉更多生命周期方法:https://reactjs.org/docs/react-component.html#the-component-lifecycle
最后一个示例演示了如何使用这些方法来解决 "parsing" 换行符分隔列表的类似问题。
class Table extends React.Component {
constructor() {
super();
this.state = {
rows: []
};
}
componentWillMount() {
this.parse(this.props.string);
}
componentWillReceiveProps(nextProps) {
this.parse(nextProps.string);
}
parse(string) {
// Dummy placeholder for asynchronous operation
this.setState({
rows: string.split("\n")
});
}
render() {
return (
<ul>
{ this.state.rows.map((row) => <li>{row}</li>) }
</ul>
);
}
}
class Sample extends React.Component {
constructor() {
super();
this.state = { string: "hello world\ngoodbye world" };
}
render() {
return (<div>
<Table string={this.state.string} />
<textarea onChange={(event) => this.setState({ string: event.target.value })} value={this.state.string} />
</div>);
}
}
ReactDOM.render(<Sample /> , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
当使用 FileReader API 选择要上传的文件时,我正在尝试在 React/Redux 应用程序中显示一些文件内容。我能够显示内容,但在 FileReader 的 onload 事件处理程序中调用 setState 导致发生无限渲染。
import _ from 'lodash';
import React, { Component } from 'react';
import { reduxForm, Field, formValueSelector } from 'redux-form';
import Button from 'material-ui/Button';
import * as actions from '../../actions';
import { connect } from 'react-redux';
import {
Select,
TextField,
} from 'redux-form-material-ui';
import { renderFileInput } from '../helpers/form_helpers';
class ImportLeads extends Component {
state = {
fields: []
}
handleFormSubmit({ leadsCSV }) {
const { listid } = this.props;
this.props.importLeads(leadsCSV, listid);
}
renderMapping() {
const { CSVFile } = this.props;
console.log(CSVFile);
const temp = [];
if(CSVFile) {
const r = new FileReader();
r.readAsText(CSVFile, "UTF-8");
r.onload = (e) => {
const content = e.target.result;
const firstLine = content.split('\n', 1)[0];
const fieldsArray = firstLine.split(',');
console.log(fieldsArray);
_.map(fieldsArray, field => {
console.log(field);
temp.push(<div>{field}</div>);
});
this.setState({ fields: temp });
}
r.onerror = function(e) {
console.log("Error reading file");
}
}
return (
<div>
{this.state.fields}
</div>
);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<div>
<Field
name="leadsCSV"
component={renderFileInput}
type="file"
text="Import CSV"
/>
<div style={{ marginTop: '10px' }}>
<Button type="submit" variant="raised">Upload</Button>
</div>
</div>
</form>
{this.renderMapping()}
</div>
);
}
}
ImportLeads = reduxForm({
form: 'importLeads',
})(ImportLeads);
const selector = formValueSelector('importLeads');
ImportLeads = connect(
state => {
const CSVFile = selector(state, 'leadsCSV');
return {
CSVFile
}
}
)(ImportLeads);
export default ImportLeads;
我使用 redux 形式处理文件输入,并通过使用 connect 使其作为 props 可用来获取文件的值。
我读到,在 render 方法内部调用 setState 时,通常会发生无限渲染。有没有更好的方法来解决这个问题而不让它无限渲染?谢谢
在 renderMapping()
内部,您调用 setState
导致 re-render 组件。
在组件 render
中,您 {this.renderMapping()}
导致再次调用 renderMapping
。
此过程重复 n 次导致无限渲染。
如果 fieldsArray
已经包含 field
,您应该签入 renderMapping
。如果是这样,你不应该把它推到数组中。 (因为你正在将另一个 div 中的相同字段推入你的数组,它不断导致 re-render)
if (fieldsArray.includes(field)) {
return;
}
React 有特殊的 "lifecycle" 方法,对这个用例非常有用。
如果您定义 componentWillMount()
方法,它将在第一次渲染之前被调用。如果您有额外的资源需要加载,您可以从那里开始。
因为看起来你想要 re-run 那些计算,如果道具改变你也会想要使用 componentWillReceiveProps(nextProps)
到 re-run 你的计算基于新的道具。请注意,this.props
尚未反映新道具,您需要从参数中获取这些道具。
最后,虽然对你的情况没有必要,但你应该熟悉 componentWillUnmount()
,如果你没有这样做,你可以自己清理(例如删除事件侦听器、取消计时器等)很容易导致内存泄漏甚至更糟。
您可以在文档中熟悉更多生命周期方法:https://reactjs.org/docs/react-component.html#the-component-lifecycle
最后一个示例演示了如何使用这些方法来解决 "parsing" 换行符分隔列表的类似问题。
class Table extends React.Component {
constructor() {
super();
this.state = {
rows: []
};
}
componentWillMount() {
this.parse(this.props.string);
}
componentWillReceiveProps(nextProps) {
this.parse(nextProps.string);
}
parse(string) {
// Dummy placeholder for asynchronous operation
this.setState({
rows: string.split("\n")
});
}
render() {
return (
<ul>
{ this.state.rows.map((row) => <li>{row}</li>) }
</ul>
);
}
}
class Sample extends React.Component {
constructor() {
super();
this.state = { string: "hello world\ngoodbye world" };
}
render() {
return (<div>
<Table string={this.state.string} />
<textarea onChange={(event) => this.setState({ string: event.target.value })} value={this.state.string} />
</div>);
}
}
ReactDOM.render(<Sample /> , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>