جافاسكريبت - لماذا يتم استدعاء عرض المكون في React مرتين، مرة بدون بيانات ثم لاحقًا بالبيانات، ولكن يظهر استثناء متأخر؟
لدي مكون TreeNav الذي تأتي بياناته من استدعاء API. لقد قمت بإعداد المخفض/الإجراء/الوعد وكل التوصيلات، ولكن عند عرض المكون عندما أقوم باستدعاء map() على البيانات، أحصل على "خطأ غير ملتقط من النوع TypeError: لا يمكن قراءة الخاصية 'map' من undefined".
أظهر استكشاف الأخطاء وإصلاحها أن TreeNav render() يتم استدعاؤه مرتين. المرة الثانية تكون بعد عودة البيانات من واجهة برمجة التطبيقات. ولكن بسبب خطأ في الاستدعاء الأول لـ render()، فإن الاستدعاء الثاني لـ render() لا يتم تنفيذه أبدًا.
إليك ملفات الكود الخاصة بي:
-------- المخفضات/index.js ---------
import { combineReducers } from 'redux';
import TreeDataReducer from './reducer_treedata';
const rootReducer = combineReducers({
treedata: TreeDataReducer
});
export default rootReducer;
-------- المخفضات/reducer_treedata.js ---------
import {FETCH_TREE_DATA} from '../actions/index';
export default function (state=[], action) {
switch (action.type) {
case FETCH_TREE_DATA: {
return [action.payload.data, ...state];
}
}
return state;
}
-------- الإجراءات/الفهرس.js --------
import axios from 'axios';
const ROOT_URL = 'http://localhost:8080/api';
export const FETCH_TREE_DATA = 'FETCH_TREE_DATA';
export function fetchTreeData () {
const url = `${ROOT_URL}/treedata`;
const request = axios.get(url);
return {
type: FETCH_TREE_DATA,
payload: request
};
}
-------- المكونات/tree_nav.js --------
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchTreeData} from '../actions/index';
class TreeNav extends Component {
constructor (props) {
super(props);
this.state = {treedata: null};
this.getTreeData();
}
getTreeData () {
this.props.fetchTreeData();
}
renderTreeData (treeNodeData) {
const text = treeNodeData.text;
return (
<div>
{text}
</div>
);
}
render () {
return (
<div className="tree-nav">
{this.props.treedata.children.map(this.renderTreeData)}
</div>
);
}
}
function mapStateToProps ({treedata}) {
return {treedata};
}
// anything returned from this function will end up as props
// on the tree nav
function mapDispatchToProps (dispatch) {
// whenever selectBook is called the result should be passed to all our reducers
return bindActionCreators({fetchTreeData}, dispatch);
}
// Promote tree_nav from a component to a container. Needs to know about
// this new dispatch method, fetchTreeData. Make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)(TreeNav);
الإجابة رقم 1
من حيث الخطأ مع العرض الثاني الخاص بك، يجب أن تكون الحالة يتم تجاوزها بطريقة لا تتوقعها. لذلك في المخفض الخاص بك، أنت تقوم بإرجاع مصفوفة تحتوي على أي بيانات موجودة، ونسخة من الحالة الحالية. مع المصفوفات، هذا يؤدي إلى دمج.
var a = [1,2,3]
var b = [a, ...[2,3,4]]
يترجم إلى:
var a = [1, 2, 3];
var b = [a].concat([2, 3, 4]);
إذن بالنظر إلى أنك تتوقع وجود خاصية children، أعتقد أن ما تريده فعليًا هو مُختصر يُرجع كائنًا، وليس مصفوفة، ويفعل شيئًا مثل هذا بدلاً من ذلك:
return Object.assign({}, state, { children: action.payload.data });
من هناك تأكد من تحديث الحالة الأولية لتكون كائنًا، مع مصفوفة فرعية فارغة.
تخلص من هذا السطر لأنك تستخدم الخصائص (props) بدلاً من الحالة (state). يمكن أن تكون الحالة مفيدة إذا كنت بحاجة لإدارة التغييرات داخليًا فقط داخل المكون. ولكنك تستخدم connect، لذلك هذا غير ضروري هنا.
this.state = {treedata: null};
الإجابة رقم 2
تم حل هذا عن طريق التحقق من وجود this.props.treedata، وما إلى ذلك، وإذا لم يكن متاحًا بعد، يجب فقط عرض div "جارٍ التحميل...".
render () {
if (this.props.treedata && this.props.treedata[0] && this.props.treedata[0].children) {
console.dir(this.props.treedata[0].children);
return (
<div className="tree-nav">
{this.props.treedata[0].children.map(this.renderTreeData)}
</div>
);
} else {
return <div>Loading...</div>;
}
}
