type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of `TopicDetail`. in TopicDetail (created by inject-TopicDetail) in inject-TopicDetail (created by WithStyles(inject-TopicDetail)) in WithStyles(inject-TopicDetail) (created by Route) in Route (created by _default) in _default (created by App) in App (created by Main) in Main in MuiThemeProvider in Router (created by BrowserRouter) in BrowserRouter in Provider in AppContainer
发生原因一般是引用了无效的组件,如果组件确实正确,看下引用的组件是否正常导出:(export default)
但是并没有找到哪里出了问题
TopicDetail 源代码:
import React from 'react' import PropTypes from 'prop-types' import marked from 'marked' import Helmet from 'react-helmet' import { inject, observer, } from 'mobx-react' import green from '@material-ui/core/colors/green' import { withStyles } from '@material-ui/core/styles' import Paper from '@material-ui/core/Paper' import { CircularProgress } from '@material-ui/core/CircularProgress' import Container from '../layout/container' import { topicDetailStyle } from './styles' import Reply from './reply' import TopicStore from '../../store/topic-store' @inject((stores) => { return { topicStore: stores.topicStore, } }) @observer class TopicDetail extends React.Component { componentDidMount() { const id = this.getTopicId() this.props.topicStore.getTopicDetail(id) } getTopicId() { return this.props.match.params.id } render() { const { classes, } = this.props const id = this.getTopicId() // 通过react-router生成的match对象获取url的params参数 const topic = this.props.topicStore.detailMap[id] // const topic = this.props.topicStore.topics[1] if (!topic) { // topic 不存在加载 loading return ( <Container> <section className={classes.loadingContainer}> <CircularProgress style={{ color: green[500] }} size={100} /> </section> </Container> ) } // 把内容放到 p 标签下,但是是直接塞入 html,不转义html标签 return [ <Container> <Helmet> <title>{topic.title}</title> </Helmet> <header className={classes.header}> <h3>{topic.title}</h3> </header> <section className={classes.body}> <p dangerouslySetInnerHTML={{ __html: marked(topic.content) }} /> {/* eslint-disable-line */} </section> </Container>, <Paper elevation={4} className={classes.replies}> <header className={classes.replyHeader}> <span>{`${topic.reply_count} 回复`}</span> <span>{`最新回复 ${topic.last_reply_at}`}</span> </header> <section> { topic.replies.map(reply => <Reply reply={reply} key={reply.id} />) } </section> </Paper>, ] } } TopicDetail.propTypes = { topicStore: PropTypes.instanceOf(TopicStore), classes: PropTypes.object.isRequired, match: PropTypes.object.isRequired, } export default withStyles(topicDetailStyle)(TopicDetail)
topic stre 源代码
import { observable, toJS, computed, action, extendObservable, } from 'mobx' import { topicSchema } from '../utils/variable-define' import { get } from '../utils/http' const createTopic = (topic) => { return Object.assign({}, topicSchema, topic) // 返回所有字段都有定义的topic对象 } /** * 为了以后扩展更加容易,创建一个类,让每个话题都放到这个类中 * 即变成类的实例,更好的控制话题 * 让数据使用 mobx 具有的特性 */ export class Topic { // 创建 topic 的时候就是所有有定义的字段都添加 observable 属性并附加this constructor(data) { extendObservable(this, data) // 把数据直接扩展到 this 上并使用 observeable } @observable syncing = false // 是否异步操作请求数据,在组件中反应正在加载的操作 } class TopicStore { @observable topics @observable details // 数组 @observable syncing // 是否正在处理数据请求 // constructor({ syncing, topics, details } = { syncing: false, topics: [], details: [] }) { constructor({ syncing = false, topics = [], details = [] } = {}) { // 整个对象默认等于 空对象 // 初始化数据,设置默认值 this.syncing = syncing this.topics = topics.map(topic => new Topic(createTopic(topic))) // 数组使用map this.details = details.map(topic => new Topic(createTopic(topic))) // 数组使用map } /** * 上面是 topic 的定义 * 下面获取 topic 数据 */ addTopic(topic) { // 往 topics 里加入新的 topic 对象 this.topics.push(new Topic(createTopic(topic))) } @computed get detailMap() { // 方便获取某个 id 下的 detail return this.details.reduce((result, detail) => { result[detail.id] = detail // eslint-disable-line return result }, {}) } @action fetchTopics(tab) { this.syncing = true // 开始请求数据之前设置为 true 表示正在异步获取数据 this.topics = [] // 获取之前先清空 return new Promise((resolve, reject) => { get('/topics', { mdrender: false, // markdown 字符串是否转义成 html 字符串,不转义还可以继续编辑 markdown tab, }).then((resp) => { if (resp.success) { // 把拿到的所有 topic 都放到 this.topics 里 resp.data.forEach((topic) => { this.addTopic(topic) }) resolve() // 数据获取成功 } else { reject() // 数据获取失败 } this.syncing = false // 数据获取结束 }).catch((err) => { // 出现任何异常 reject(err) this.syncing = false // 出现异常数据获取结束 }) }) } @action getTopicDetail(id) { return new Promise((resolve, reject) => { if (this.detailMap[id]) { // 有数据,即已经获取过该id的详情数据 resolve(this.detailMap[id]) } else { // 没有就获取 get(`/topic/${id}`, { mdrender: false, // 不要转化 markdown 格式 }).then((resp) => { if (resp.success) { const topic = new Topic(createTopic(resp.data)) // 新建一个 topic 对象 this.details.push(topic) // 获取的数据放入 details 数组中 resolve(topic) // 就可以很方便的根据id去 detailMap 获取有详情的话题对象 } else { reject() } }).catch(reject) } }) } toJson() { return { topics: toJS(this.topics), syncing: this.syncing, details: toJS(this.details), } } } export default TopicStore