1
创建React应用
npx create-react-app my-app
版本需求:
Node >= 6 and npm >= 5.2
react和react-dom的区别
React开发的核心是react.js
和react-dom.js
,分别有React和ReactDOM核心对象
React对象包含了生成虚拟DOM的函数React.createElement()
以及Component
这个类
ReactDOM对象的核心功能就是把这些虚拟DOM渲染到文档中变成实际的DOM
ReactDOM.render(Component,element)
—将组件渲染到页面中某一个dom中
ReactDOM.unmountComponentAtNode(element)
--将页面的某个元素中渲染的组件卸载掉
serviceWorker
**service worker
**是在后台运行的一个线程,可以用来处理离线缓存、消息推送、后台自动更新等任务。registerServiceWorker
就是为react项目注册了一个service worker
,用来做资源的缓存,这样你下次访问时,就可以更快的获取资源。而且因为资源被缓存,所以即使在离线的情况下也可以访问应用(此时使用的资源是之前缓存的资源)。注意,registerServiceWorker
注册的service worker
只在生产环境中生效
推荐在JSX代码的外面扩上一个小括号
原因:可以防止分号自动插入的BUG
Javascript's automatic semicolon insertion
ASI
分号自动插入BUG主要发生在此处:
return
"something";
// 会被翻译成如下
return;
"something";
JSX单一根节点
React组件只能渲染一个根节点,这并非JSX的限制,而是JS的一个特性:一条返回语句只能返回单个值
JSX防注入攻击
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。
Props的只读性
纯函数
一个函数的返回结果只依赖于它的参数,并且在执行过程中没有副作用,这个函数就是纯函数
- 函数的返回结果只依赖于它的参数
const a = 1
const foo = (b) => a + b
foo(2) // => 3
foo
函数不是一个纯函数,因为它返回的结果依赖于外部变量 a,我们在不知道 a 的值的情况下, 并不能保证foo(2)
的返回值是3。虽然foo
函数的代码实现并没有变化,传入的参数也没有变化,但它的返回值却是不可预料的,现在,foo(2)
的值是3,可能过一会就是4了,因为a的值可能发生了变化,由1变成了2。
const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3
现在 foo
的返回结果只依赖于它的参数 x 和 b,foo(1, 2)
永远是 3。今天是 3,明天也是 3,在服务器跑是 3,在客户端跑也 3,不管你外部发生了什么变化,foo(1, 2)
永远是 3。只要 foo
代码不改变,你传入的参数是确定的,那么 foo(1, 2)
的值永远是可预料的。
- 函数执行过程中没有副作用
一个函数执行过程产生了外部可观察的变化那么就说这个函数是有副作用的,即纯函数不应该产生外部可观察的变化
const a = 1
const foo = (obj, b) => {
return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 3
counter.x // => 1
// 修改一下 foo,把原来的 x 换成了 obj,我现在可以往里面传一个对象进行计算,计算的过程里面并不会对传入的
// 对象进行修改,计算前后的 counter 不会发生任何变化,计算前是 1,计算后也是 1,它现在是纯的。但是再稍微
// 修改一下它
const a = 1
const foo = (obj, b) => {
obj.x = 2
return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 4
counter.x // => 2
// 现在情况发生了变化,我在 foo 内部加了一句 obj.x = 2,计算前 counter.x 是 1,但是计算以后 counter.x
// 是 2。foo 函数的执行对外部的 counter 产生了影响,它产生了副作用,因为它修改了外部传进来的对象,现在
// 它是不纯的。
// 但是你在函数内部构建的变量,然后进行数据的修改不是副作用
const foo = (b) => {
const obj = { x: 1 }
obj.x = 2
return obj.x + b
}
虽然 foo
函数内部修改了 obj,但是 obj 是内部变量,外部程序根本观察不到,修改 obj 并不会产生外部可观察的变化,这个函数是没有副作用的,因此它是一个纯函数。
除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API
修改页面,或者你发送了 Ajax 请求,还有调用window.reload
刷新浏览器,甚至是console.log
往控制台打印数据也是副作用。
纯函数很严格,也就是说你几乎除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据。
为什么要煞费苦心地构建纯函数?因为纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。不管何时何地,你给它什么它就会乖乖地吐出什么。如果你的应用程序大多数函数都是由纯函数组成,那么你的程序测试、调试起来会非常方便。
State
- 构造函数是唯一能够初始化
this.state
的地方 - 状态更新是异步的
React可以将多个
setState()
调用合并成一个调用来提高性能。因为
this.prosp
和this.state
可能是异步更新的,所以我们不应该依靠他们的值来计算下一个状态。当
setState()
接受的是一个函数而不是一个对象时,该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props作为第二个参数。
this.setState((prevState,props)=>{
counter: prevState.counter + props.increment
})
自顶向下或单向数据流
- 状态为什么除了拥有并设置它的组件外,其他组件不可访问?
父组件或子组件都不能知道某个组件是有状态的还是无状态的,并且它们不应该关心某个组件是被定义为一个函数还是一个类。
- 任何状态始终由某个特定组件所有,并且从该状态导出的任何数据或UI组件只能影响树中下方的组件。
向事件处理程序传递参数
- 通过箭头函数的方式,事件对象必须显示的进行传递,但是通过bind的方式,事件对象以及更多的参数将会被隐式的进行传递。
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
- 通过bind方式向监听函数传参,在类组件中定义的监听函数,事件对象event要排在所传递的参数的后面。
条件渲染
在条件渲染时,可以使用元素变量来存储元素
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
阻止组件进行渲染
- 让
render
方法返回null
则可以阻止组件的渲染 - 组件的
render
方法返回null
并不会影响组件生命周期方法的回调,例如,componentWillUpdate
和componentDidUpdate
依然可以被调用。
Keys
- keys可以在DOM中的某些元素被增加或删除的时候帮助React识别那些元素发生了变化。
- 元素的keys只有在它和它的兄弟节点对比时才有意义。
- key只会作为React的提示,但不会传递给你的组件。
<Blog post={post} key={123}>
<Post
id={post.id}
title={post.title}
key={post.key} // Post组件可以读出props.id,但是不能读出props.key
/>
Reconciliation 协调
目的
当你使用React的时候,在单一时间点你可以考虑render()
函数作为创建React元素的树。在下一次状态或是属性更新的时候,render()
函数将会返回一个不同的React元素的树。React需要算出如何高效的更新UI以匹配最新的树。
React基于两点假设,实现了一个启发的O(n)算法:
- 两个不同类型的元素将产生不同的树
- 通过渲染器附带的key属性,开发者可以示意哪些子元素可能是稳定的。
对比算法
对比两棵树时,React首先比较两个根节点。根节点的type不同,其行为也不同。
-
不同类型的元素。
每当根元素有不同的类型,React将卸载旧树并重新构建新树。当树被卸载,旧的DOM节点将被销毁。组件实例会调用
componentWillUnmount()
。当构建一棵新树,新的DOM节点被插入到DOM中。组件实例将依次调用componentWillMount()
和componentDidMount()
。 任何与旧树有关的状态都将被丢弃// 旧的Counter将会被销毁,新的Counter将会被重新装载 <div> <Counter /> </div> ===>>> <span> <Counter /> </span>
-
相同类型的DOM元素
当比较两个相同类型的React DOM元素时,React则会观察二者的属性,保持相同的底层DOM节点,并仅更新变化的属性。
<div className="before" title="stuff" /> <div className="after" title="stuff" />
通过比较两个元素,React知道仅更改底层DOM元素的
className
。当更新
style
时,React同样知道仅更新变更的属性。<div style={{color: 'red', fontWeight: 'bold'}} /> <div style={{color: 'green', fontWeight: 'bold'}} />
当在调整两个元素时,React知道仅改变
color
样式而不是fontWeight
。在处理完DOM元素后,React递归其子元素。
-
相同类型的组件元素
当组件更新时,实例仍保持一致,以让状态能够在渲染之间保留。React通过更新底层组件实例的props来产生新元素,并在底层实例上依次调用
componentWillReceiveProps()
和componentWillUpdate()
方法。接下来,
render()
方法被调用,同时对比算法会递归处理之前的结果和新的结果。 -
递归子节点
-
keys
我们可以用数组的index索引作为key,若元素没有重新排序,该方法效果不错,但重排会使其变得很慢。
当索引用于key时,组件状态在重新排序时也会有问题。组件实例基于key进行更新和重用。如果key是索引,则item的顺序变化会改变key值。这将导致非受控组件的状态可能会以意想不到的方式混淆和更新。
权衡
在目前实现中,可以表明一个事实,即子树在其兄弟节点中移动,但你无法告知其移动到哪。该算法会重渲整个子树。
由于React依赖于该启发式算法,若其背后的假设没得到满足,则其性能将会受到影响:
- 算法无法尝试匹配不同组件类型的子元素。若你发现两个输出非常相似的组件类型交替出现,你可能希望使其成为相同类型。实践中,我们并非发现这是一个问题。
- Keys应该是稳定的,可预测的,且唯一的。不稳定的key(类似由
Math.random()
生成的)将使得大量组件实例和DOM节点进行不必要的重建,使得性能下降并丢失子组件的状态。
受控组件与非受控组件
-
eet6r受控组件
值由React控制的输入表单元素称为“受控组件”。
使用“受控组件”,每个状态的改变都有一个与之相关的处理函数。
如
<input type='text'>
,<textarea>
,<select>
通过传入value
属性实现对组件的控制。受控组件中表单数据由React组件处理。
-
非受控组件
非受控组件中表单数据由DOM处理。
`<input type='file'>`标签的`value`属性是只读的,所以它是一个非受控组件。
-
多个输入的解决方法
当有多个受控的
input
元素时,你可以通过给每个元素添加一个name
的属性来让处理函数根据event.target.name
的值来选择做什么。this.setState({ [name]: value });
React组件可以接受任意元素,包括基本数据类型,React元素或函数。
Key是唯一可以传递给Fragment的属性。在将来可能增加额外的属性支持,比如事件处理
上一篇: 多项式求值
下一篇: 使用二维数组打印一个 10 行杨辉三角