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