我希望用ReactJS创建一个单页面的应用程序。
是否可以将它与角度结合使用或仅适用于它自己?我想用一个部分填充单页网站 - 添加各种功能,如旋转木马,滑块,同位素过滤器......
<!DOCTYPE html>
<html>
<head>
<title>React Js one page</title>
<script src="https://fb.me/react-with-addons-0.14.7.min.js"></script>
<script src="https://fb.me/react-dom-0.14.7.min.js"></script>
</head>
<body>
<section>
One
<script>
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="Colonel Mustard" />, mountNode);
</script>
</section>
<section>
Two
<script>
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
ReactDOM.render(<CommentBox />, mountNode);
</script>
</section>
<section>
Three
<script>
"use strict";
var MarkdownEditor = React.createClass({
displayName: "MarkdownEditor",
getInitialState: function getInitialState() {
return { value: 'Type some *markdown* here!' };
},
handleChange: function handleChange() {
this.setState({ value: this.refs.textarea.value });
},
rawMarkup: function rawMarkup() {
return { __html: marked(this.state.value, { sanitize: true }) };
},
render: function render() {
return React.createElement(
"div",
{ className: "MarkdownEditor" },
React.createElement(
"h3",
null,
"Input"
),
React.createElement("textarea", {
onChange: this.handleChange,
ref: "textarea",
defaultValue: this.state.value }),
React.createElement(
"h3",
null,
"Output"
),
React.createElement("div", {
className: "content",
dangerouslySetInnerHTML: this.rawMarkup()
})
);
}
});
ReactDOM.render(React.createElement(MarkdownEditor, null), mountNode);
</script>
</section>
</body>
</html>
答案 0 :(得分:6)
如果您刚开始使用React,我强烈建议您关注Pete Hunt's advice:
你会知道什么时候需要Flux。如果您不确定是否需要它,则不需要它。
最好的办法是自行开始使用React,并使用每个组件附带的本地state
管理应用程序状态。
当您发现必须将数据传递回父组件时,请将Flux添加到组合中并重写您的有状态组件,而不是使用Flux存储。
我们将了解如何从下到上处理将StackOverflow应答组件的简化版本编写为React应用程序。
注意,我说的是React应用程序,而不是React组件。这是因为没有技术差异。 React应用程序是一个很大的React组件,由许多较小的组件组成。
一旦你有一个应用程序的界面(从线框到html / css的任何东西),你可以直观地细分它们,以确定它们如何作为React组件组合在一起。
关于你如何确定应该或不应该是它自己的组件,没有严格而快速的规则,但是你做的次数越多,就会感觉到它。
<Answer />
<Votes />
<AnswerText />
<AnswerActions />
因为我们是自下而上构建的,所以我们首先要实现每个子组件并测试它们是否单独工作。
在开发生命周期的这一点上,我们只为每个组件编写静态标记。还没有必要考虑道具或陈述。
我们可以使用无状态组件语法来开始我们已经识别的组件。这是我们如何编写<Votes />
组件的示例。
function Votes() {
return (
<div>
<a>▲</a>
<strong>0</strong>
<a>▼</a>
</div>
);
}
当然,这并没有做任何事情,但它允许我们开始编写组件以了解应用程序的结构。
我们可以将它渲染到DOM中以检查它是否可以随时工作。
ReactDOM.render(<Votes />, document.getElementById('app'));
一旦您完成了其他组件的静态版本的实现,您可以将它们放在一起以创建父<Answer />
组件。
function Answer() {
return (
<div>
<Votes />
<AnswerText />
<AnswerActions />
</div>
);
}
接下来要做的是弄清楚数据在应用程序中的流动方式。
此时我们可以以答案对象的形式创建一些虚拟数据,如下所示:
{
"id": 0,
"votes": 0,
"text": "This is an answer"
}
最初,我们可以通过将此答案对象作为道具传递给它来呈现<Answer />
组件。
<Answer answer={answer} />
现在,该组件的工作是将适当的数据传递给其子级。
显然不是每个孩子都需要所有的数据,所以我们必须决定哪些数据去哪里。让我们更新我们的<Answer />
组件。
function Answer(props) {
var answer = props.answer;
return (
<div>
<Votes id={answer.id} count={answer.votes} />
<AnswerText text={answer.text} />
<AnswerActions id={answer.id} />
</div>
);
}
<Votes />
组件需要知道当前的投票数,并且还需要知道答案的id
,以便它可以将更改传达给服务器。
<AnswerText />
组件只会呈现一个文本块,因此我们需要传递它。
最后,<AnswerActions />
组件呈现一个链接列表,允许用户对答案执行某些操作(共享,编辑,标记)。该组件还需要答案id
,以便它可以与服务器通信。
现在我们必须依次更新这些子组件以使用这些新的动态值,而不是我们最初使用的静态值。我们将重新审视<Votes />
组件,看看是否会发生这种情况。
function Votes(props) {
var urls = {
upvote: '/api/answers/' + props.id + '/upvote',
downvote: '/api/answers/' + props.id + '/downvote'
};
return (
<div>
<a href={urls.upvote}>▲</a>
<strong>{props.votes}</strong>
<a href={urls.downvote}>▼</a>
</div>
);
}
现在,当我们点击投票按钮时,我们的投票组件将向相应的端点发出HTTP请求,但是,我们宁愿在不重新加载和重新呈现整个应用程序的情况下进行此更新。
组件开发过程的最后一部分是识别有状态组件。这些组件具有在应用程序生命周期内会发生变化的移动部件和数据。
每次组件内的状态发生变化时,整个组件都会重新渲染。我们可以重新访问线框,看看我们的哪些组件需要管理不断变化的数据。
此应用程序只有一个有状态组件()和那个``。当我们点击其中一个箭头时,我们需要更新数字以反映新的计数。
它是我们需要重新渲染的唯一一个组件。
这意味着我们需要升级组件才能使用React的createClass
语法。这允许它开始管理它自己的状态。
var Votes = React.createClass({
getInitialState: function() {
return { votes: this.props.votes };
},
upvote: function() {
var newVotes = this.state.votes + 1;
this.setState({
votes: newVotes
});
},
downvote: function() {
var newVotes = this.state.votes - 1;
this.setState({
votes: newVotes
});
},
render: function() {
return (
<div>
<a onClick={this.upvote}>▲</a>
<strong>{this.state.votes}</strong>
<a onClick={this.downvote}>▼</a>
</div>
);
}
});
我已经跳了一下枪并实施了完整的组件,但希望你能理解。
首先,我们使用getInitialState
设置一些状态来表示组件中的初始投票数。
接下来,我们实施更新组件状态的upvote
和downvote
组件方法。
最后我们重新实现了之前的render方法,但让箭头触发新的组件方法,而不是页面请求。
每次调用setState
时,React都会重新渲染组件。希望您能看到为什么我们将状态放在<Votes />
组件而不是<Answer />
组件中。重新渲染答案文本和行动会很疯狂,因为投票已经改变了。
一旦我们确定并实施了所有有状态组件,我们就可以开始将其状态转移到Flux商店。
真正的应用程序更有可能拥有<AnswerStore />
而不是<VoteStore />
,这是我们实施的目标。现在我们只是在嘲笑我们的数据。
var AnswerStore = {
_listeners: [],
_answers: {
"0": {
"id": 0,
"votes": 0,
"text": "This is an answer"
}
},
get: function(id) {
return this._answers[id];
},
update: function(id, update) {
var answer = this.get(id);
var updatedAnswer = Object.assign({}, answer, update);
this._answers[id] = updatedAnswer;
this.emit();
},
listen: function(f) {
this._listeners.push(f);
},
emit: function() {
this._listeners.forEach(function(f) {
f();
});
}
};
在这个例子中,我编写了一个包含数据的相当通用的商店,提供了用于监听模型更改的简单处理程序,最后公开了用于改变商店中数据的方法。
非常重要的是,我们的update
方法将单个答案视为此应用程序中的不可变项,否则我们冒险改变应用程序的其他部分引用的对象,从而导致对象在下面发生更改他们。我们每次使用Object.assign
创建一个新对象,基于旧对象。
我们需要做的下一件事是为这个商店设置一些动作处理程序。
dispatcher.register(function(action) {
switch(action.type) {
case 'UPVOTE':
var votes = ActionStore.get(action.id);
ActionStore.update(action.id, { votes: votes + 1 });
break;
case 'DOWNVOTE':
var votes = ActionStore.get(action.id);
ActionStore.update(action.id, { votes: votes - 1 });
break;
}
});
这只是将更新方法连接到两个名为'UPVOTE'
和'DOWNVOTE'
现在我们将Flux连接到需要以长格式重写的<AnswerComponent />
。
var Answer = React.createClass({
getInitialState: function() {
return { answer: {} };
},
componentWillMount: function() {
this.update();
AnswerStore.listen(this.update);
},
update: function() {
var id = this.props.id;
this.setState({ answer: AnswerStore.get(id) });
},
render: function() {
var answer = this.state.answer;
return (
<div>
<Votes id={answer.id} count={answer.votes} />
<AnswerText text={answer.text} />
<AnswerActions id={answer.id} />
</div>
);
}
});
在我们的componentWillMount
方法中,我们获取商店的初始数据,然后在商店中设置一个侦听器,只要商店发生变化,就会获取并更新组件状态。
最后,我们需要一种从<Votes />
组件调度相应操作的方法。
最常用的方法是使用动作创建者。动作创建者是一个函数,它将一些数据作为参数,然后打包并将其作为动作发送。
var Actions = {
upvote: function(id) {
dispatcher.dispatch({
type: 'UPVOTE',
id: id
});
},
downvote: function(id) {
dispatcher.dispatch({
type: 'DOWNVOTE',
id: id
});
}
};
然后我们从<Votes />
组件(可以再次成为无状态组件)中调用这些操作。
function Votes(props) {
var id = props.id;
return (
<div>
<a onClick={Actions.upvote.bind(null, id)}>▲</a>
<strong>{props.votes}</strong>
<a onClick={Actions.downvote.bind(null, id)}>▼</a>
</div>
);
}
此组件现在使用操作创建者为我们的Flux商店调度操作来处理。
如果我们通过我们的应用程序查看数据流,我们可以看到我们现在有一个单向循环,而不是树。
<Answer />
组件将id
向下传递给<Votes />
组件。<Votes />
组件使用id
AnswerStore
处理操作并发出更改。<Answer />
组件会听到更新并更新其状态,并重新呈现其子项。这是此演示应用程序的jsfiddle。
这是一个非常简单的组件,只处理少量的数据流,甚至更少的应用程序状态,但是,它足以向您展示如何编写React组件以及您需要的所有内容构建一个React应用程序。
我们假设我们正在将StackOverflow问题作为React应用程序实现。
function App() {
return (
<Page>
<Navigation />
<SideBar>
<MetaDetails />
<Ads />
<RelatedQuestions />
</SideBar>
<Question />
<AnswerList />
<AnswerEditor />
<Footer />
</Page>
);
}
它可能看起来像一个复杂的应用程序,但您可以将其分解并将其表示为不同的组件,然后您可以单独实现和测试组件,就像我们在此处所做的那样,并将它们完全组合在一起以形成复杂的应用程序。 / p>
对于像这样的大多数简单的React应用程序,Flux实际上并不是必需的。值得记住的是,React是在Flux发布一年之前发布的,但它被许多开发人员采用,无论如何。
当然,我在这里只介绍了构建和实现组件。从线框到部署的应用程序是一个复杂得多的过程,并且在一个答案中无法详细介绍它。最后,您可能还想了解:
需要一些时间才能完成这些事情,诀窍不是急于求成。在您找到这些解决方案存在的原因之前,不要过度复杂现有项目。
我认为react-howto是最好的指南。虽然它在细节上并不重要,但它与很多优秀的资源相关联,最重要的是它提供了一个固执的指南,指导您在成为合格的ReactJS开发人员的过程中学习这些技术的顺序。
答案 1 :(得分:0)
框架的选择(Angular / React)并不妨碍构建您描述的任何功能,并且您的站点既不能构建,也不能同时构建这两个框架。
答案 2 :(得分:0)
虽然你肯定可以组合Angular和React,但我不确定你为什么会这样做,而且它不会是最简单的任务。当然,这是可行的,但它会为极少的最终收益提供很多困难。
如果您想使用React构建SPA,我会更专注于寻找您喜欢的Flux实现,并学习如何集成它。 Flux专门针对React进行设计,以处理可能具有复杂数据流的大型SPA,并且它当然是一种更容易在早期合并的工具。
我会考虑出门的唯一其他库是Immutable.js,它与React和Flux配对得非常好。
但除此之外,在找到 need 引入其他框架/库之前,请尝试推迟。有了所有令人兴奋的JS框架,想要全部使用它们很诱人,但实际上你最好选择一个专注于它,然后也许带来在以后的某些工具中有必要的时候使用它们。