我认为在使用React.js进行服务器端渲染方面,我概念上缺少了一些东西。
假设我想创建一个页面来显示来自服务器端数据库的项目,并带有一个输入字段以进行筛选。
我想要一个页面:
- 响应类似于
/items?name=foobar
的URL - 带有用于按名称筛选项目的React输入字段
- 带有用于显示经过筛选的项目列表的React组件
假设我有一个公共REST API来查询客户端上的项目。
从概念上讲,在第一次请求 (GET /items?name=foobar
) 时,我想要做的是:
- 我希望我的输入字段显示用户传递的参数,因此我需要将查询参数('foobar')作为“prop”(例如
initialName
)传递给React组件。
所以我尝试了这个:
// A stateful component, maintaining the value of a query field
var ItemForm = React.createClass({
getInitialState : function () {
return {
name : this.props.initialName
};
},
handleInputValueChanged : function() {
var enteredName = this.refs.query.getDOMNode().value.trim();
this.props.onNameChanged(enteredName);
},
render : function () {
return React.DOM.form({
children : [
React.DOM.label({
children : "System name"
}),
React.DOM.input({
ref : "query",
value : this.state.name,
onChange : this.handleInputValueChanged
})
]
});
}
});
- 我还需要在服务器上执行数据库查询,获取项目列表,并将此项目列表作为ItemList的“prop”(如“initialItems”)传递
据我所知,我需要一个简单的组件来显示列表,将其作为“prop”接收:
// A stateless component, displaying a list of item
var ItemList = return React.createClass({
propTypes : {
items : React.PropTypes.array
},
render : function () {
return React.DOM.ul({
children : _.map(this.props.items, function (item) {
return React.DOM.li({
children : [
React.DOM.span({
children : ["Name : ", item.name].join(" ")
})]
});
})
});
}
});
- 现在,我需要一个组件来显示整个页面;该组件将需要维护整个页面的状态,即查看字段中输入的名称,并进行API查询以更新列表项。 然而,我不明白如何让这个组件拥有一个“initialState”,既可以在服务器端工作,也可以在客户端后续渲染时使用。
我尝试了这个:
// A stateful react component, maintaining the list of items
var ItemPage = React.createClass({
getInitialState : function () {
// ?????
// This is where I'm sure the problem lies.
// How can this be known both on server and client side ?
return {
items : this.props.initialItems || []
};
},
queryItems : function (enteredName) {
var self = this;
// The field was emptied, we must clear everything
if (!enteredName) {
this.setState({
items : []
});
} else {
// The field was changed, we want to do a query
// then change the state to trigger a UI update.
// The query code is irrelevant, I think.
doQuery(enteredName).then(function (items) {
self.setState({
items : items
});
});
}
},
render : function () {
// I don't want to display the same view
// if there is no results.
// This uses a 'state' property of the page
var results = null;
if (_.isEmpty(this.state.items)) {
results = React.DOM.div({
ref : "results",
children : "No results"
});
} else {
results = ItemListView({
// Here items is a 'prop', the ItemList is technically
// stateless
items : this.state.items
});
}
return React.DOM.div({
children : [
ItemForm({
initialName : this.props.initialName,
onNameChanged : this.queryItems
}),
results
]
});
}
});
我现在遇到的问题是:我可以使用类似下面的代码在服务器端渲染内容:
var name = // The name from the query parameters
var items = doQueryOnServerSide(name);
React.renderComponentAsString(ItemPage({
initialName : name,
initialItems : items
});
但是当我尝试编写客户端JavaScript时,我该怎么做?我知道我想要渲染dom的位置,但是我应该向React组件传递哪些初始属性呢?
React.renderComponent(ItemPage({
initialName : ??? // Read the name parameter from URL ?
initialItems : ??? // I don't know yet, and I don't want to make an API call until the user entered something in the input box
});
我尝试的大多数方法最终导致DOM在客户端被“擦除”,并显示以下消息:
React尝试在容器中重复使用标记,但校验和无效。这通常意味着您正在使用服务器渲染,但在服务器上生成的标记与客户端预期的不同。React注入了新的标记来进行补偿,这可以工作,但您失去了许多服务器渲染的好处。相反,请查明为什么在客户端或服务器上生成的标记不同。
请注意,只有我的ItemList组件的标记被擦除,因此我认为这是我的组件实现问题,但我真的不知道从哪里开始。
我走对路了吗?还是我完全错过了什么?