指令
如果你熟悉 Angular,那么思考 React 工作原理的方法就是想象使用 Angular 时只使用指令。 React 没有任何控制器、服务、工厂或依赖注入的概念。 If 只关注组件(Angular 术语中的指令)。
这也是 Angular 对 Angular 2 的领导方式。Angular 2 引入了一个称为组件的概念,并删除了指令、控制器和服务/工厂的概念。 Angular 2 仍然使用 DI,但你没有将你的类绑定到 Angular 世界(你在 Angular 1 中所做的)。
作用域
所以 Angular 使用作用域进行数据绑定,任何绑定到作用域(或父作用域)的模板都可以从该作用域读取和打印数据,甚至可以修改该作用域。 React 中没有范围的概念,主要是因为 React 不像 Angular 那样进行脏检查,还因为 React 使用常规的 Javascript 范围来确定视图可用的变量/对象。稍后会详细介绍。
模板
这是一个重要的区别。在 Angular 中,您可以在不同的文件中或作为 Javascript 字符串定义模板。在 React 中,您可以在 Javascript 或 JSX 中定义您的视图。 JSX 是对 Javascript 的一种类似 XML/HTML 的语言扩展,可让您描述 HTML(或原生视图,如 React Native)。
在 JSX 中,您可以将元素的属性值设置为像 <div className="myClass"> 这样的字符串或使用 Javascript 表达式,如下所示:<div className={myClassVariable}> 其中myClassVariable 是一个常规的 Javascript 变量。 JSX 中 { 和 } 之间的任何内容都只是普通的旧 Javascript。您可以传递一个对象、一个函数、一个字符串等。当您尝试在 JSX 中使用未定义的变量时,您的 linter 可以为您提供帮助,而在 Angular 模板中使用属性时您的 linter 无法做到这一点。
通过在 JSX 而不是 HTML 字符串中定义视图,您可以使用 Javascript 的全部功能。您不需要像 Angular 范围这样的东西,因为您已经有一个 Javascript 范围,它决定了您可以在视图中使用什么。这就是为什么擅长 Angular 只会让你擅长 Angular,而擅长 React 也会让你成为更好的 Javascript 程序员。
数据绑定/变异/状态
Angular 使用范围来定义应用程序状态。该范围可以从视图、控制器或指令中改变。范围相互继承,因此如果您可以访问范围,还可以修改父范围。这是大型 Angular 应用程序往往难以管理的原因之一,因为应用程序的状态可以从很多地方更改。并且观察那些触发其他变化的变化会使它更难掌握。
React 使用了两个概念,称为 props 和 state。把它们想象成常规的 Javascript 函数。状态是在函数中定义的变量,道具是传递给函数的参数。
函数中定义的变量可以在该函数中更改,并且可以作为参数传递给其他函数。
但是传递给函数的参数永远不应该在接收它们的函数中改变。他们可以创建一个局部变量并将其值分配给参数值并更改该局部变量。但它不应该直接改变论点。
所以 props 是从父组件传递给组件的值。接收 props 的组件并不拥有它们,也不知道它们来自哪里,就像函数的参数一样。另一方面,状态由组件拥有,组件可以按照自己想要的任何方式更改状态,就像局部变量一样。
React 知道组件的 state 和 props 何时更改,因为当您想要更改组件的状态时,您必须显式调用 setState。它知道 props 何时更改,因为您在父组件呈现时将 props 传递给组件。
当状态改变时,React 会重新渲染组件(及其所有子组件)。请注意,它只会将它们重新呈现为组件的虚拟表示。然后,它对自上次渲染以来发生的变化进行比较,只有实际的变化才会应用于 DOM。这本质上是 React 的秘诀。编程模型是在每次发生某些事情时重新渲染所有内容,但只执行所需的最少工作量。
我的控制器在哪里!
就像我说的,React 没有任何控制器的概念,它只关注组件。也就是说,当你使用 React 时,你仍然经常使用控制器/视图分离。您拥有处理数据获取和状态管理的组件(有时称为视图控制器),但很少进行渲染。相反,您有一个单独的组件,它对数据获取知之甚少,而对渲染知之甚少。所以视图控制器组件知道如何获取数据,然后将数据传递给知道如何渲染它的组件。一个简单的例子是这样的:
var TodoItemsComponent = React.createClass({
getInitialState: function () {
return {
todoItems: null
}
},
componentDidMount: function () {
var self = this;
TodoStore.getAll().then(function (todoItems) {
self.setState({todoItems: todoItems});
});
TodoStore.onChange(function (todoItems) {
self.setState({todoItems: todoItems});
});
},
render: function () {
if (this.state.todoItems) {
return <TodoListComponent todoItems={this.state.todoItems} />;
} else {
return <Spinner />;
}
}
});
var TodoListComponent = React.createClass({
render: function () {
return (
<ul>
{this.props.todoItems.map(function (todo) {
return <li>{todo.text}</li>;
})}
</ul>
);
}
});
在此示例中,有两个组件。一种只关心数据获取,一种只关心渲染。它们都是 React 组件,但它们的职责截然不同。这是 Angular 中控制器和指令的分离,但 React 不会强迫你这样做。
数据绑定
Angular 使用数据绑定来保持视图与视图模型同步。 React 根本不使用数据绑定。你可以说 Angular 监控视图模型的变化并相应地更新 DOM,而 React 监控你从组件返回的 JSX 的变化,并相应地更新 DOM。
关注点分离
很多人对 React 持怀疑态度,因为他们认为 React 没有很好地分离关注点。而 JSX 通常是该论点的目标。他们觉得在你的 Javascript 中添加标记是在混合对视图和行为的关注。如果您习惯 Angular,您可能不同意在标记中描述行为是一个坏主意(因为您也在 Angular 中这样做)。一个经常被吹捧的反驳论点是 React “分离关注点,而不是技术”,因为视图(标记)和它的行为不是单独的关注点,而只是传统上分离的技术(HTML 和 Javascript)。通过将行为和标记放在一起,您可以获得很多好处:
- 很容易查看您是否有未使用的变量或函数。使用 Angular,您必须在模板中查找表达式,并查找所有有权访问该范围的位置,以查看该范围内是否有未使用的变量或函数。
- 组件被隔离到一个文件中,您不必在 Javascript 文件和模板文件之间来回切换。
- 更改行为通常需要更改标记,反之亦然。因此,将其保存在一个文件中可以更轻松地查看需要进行哪些更改。
事实证明这是一堵文字墙,所以如果有什么我需要澄清或扩展的地方,请告诉我。