共计 7349 个字符,预计需要花费 19 分钟才能阅读完成。
React 根底
JSX
const element = <h1>Hello, world!</h1>;
JSX,既不是字符串也不是 HTML,实质上是一个 JavaScript 的语法扩大,且更靠近于 JavaScript,是通过 React.createElement()
创立的一个对象,称为React 元素
。
React 不强制应用 JSX,但将标记与逻辑放在一起造成 组件
,实现关注点拆散。同时,JSX 可能避免 XSS 注入攻打。
元素渲染
- React 元素是不可变对象。一旦被创立,你就无奈更改它的子元素或者属性。
更新 UI 惟一的形式是创立一个全新的元素,并将其传入ReactDOM.render()
。 - React 只更新它须要更新的局部。
React DOM 会将元素和它的子元素与它们之前的状态进行比拟,并只会进行必要的更新来使 DOM 达到预期的状态。
组件 &Props
- 函数组件:接管惟一带有数据的“props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它实质上就是 JavaScript 函数。
- class 组件:形如
function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;
}
}
- Props 的只读性: 所有 React 组件都必须像纯函数一样爱护它们的 props 不被更改。
- state 容许 React 组件随用户操作、网络响应或者其余变动而动静更改输入内容。
组件无论是应用函数申明还是通过 class 申明,都决不能批改本身的 props。
这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且屡次调用下雷同的入参始终返回雷同的后果。
React 实战视频解说:进入学习
State& 生命周期
setState(updater,[callback])
在 React 中,如果是由 React 引发的事件处理
(比方通过 onClick 引发的事件处理),调用 setState 不会同步更新 this.state
,
为什么要异步?如果 setState 是同步更新 state,而 state 的更新又会触发组件的从新渲染,那么每次 setState 都会渲染组件,这对性能是很大的耗费。
- 失常 React 绑定的事件:异步更新
- 通过 addEventListener 绑定的事件:同步更新
- 通过 setTimeoutt 解决点击事件:同步更新
应用 compoentDidUpdate
或 setState
的回调函数,来保障在更新利用后触发。
批量更新,是基于一个队列和一个变量锁 isBatchingUpdates
实现。
正确地应用 State 的姿态:
- 不要间接批改 State
- 调用 setState 不会立刻更新
- 所有组件应用的是同一套更新机制,当所有组件 didmount 后,父组件 didmount,而后执行更新
- 更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
- 钩子函数和合成事件中:
在 react 的生命周期和合成事件中,react 依然处于他的更新机制中,这时 isBranchUpdate 为 true
。
依照上述过程,这时无论调用多少次 setState,都会不会执行更新,而是将要更新的 state 存入_pendingStateQueue
,将要更新的组件存入dirtyComponent
。
当上一次更新机制执行结束,以生命周期为例,所有组件,即最顶层组件 didmount
后会将 isBranchUpdate
设置为false
。这时将执行之前累积的setState
。
- 异步函数和原生事件中
由执行机制看,setState 自身并不是异步的
,而是如果在调用 setState 时,如果 react 正处于更新过程,以后更新会被暂存,等上一次更新执行后在执行,这个过程给人一种 异步的假象
。
在生命周期,依据 JS 的异步机制,会将异步函数先暂存,等所有同步代码执行结束后在执行,这时上一次更新过程曾经执行结束,
isBranchUpdate
被设置为false
,依据下面的流程,这时再调用 setState 即可立刻执行更新,拿到更新后果。
- componentDidMount 调用 setstate
它将会触发一次额定的渲染,然而它将在浏览器刷新屏幕之前产生。这保障了在此状况下即便 render()将会调用两次,用户也不会看到中间状态。
componentDidMount 自身处于一次更新中
,咱们又调用了一次 setState,就会在将来再进行一次 render,造成不必要的性能节约,大多数状况能够设置初始值来搞定。
componentWillUpdate
、componentDidUpdate
不能调用 setState, 会造成死循环,导致程序解体。- 举荐:在调用 setState 时应用函数传递 state 值,在回调函数中获取最新更新后的 state。
生命周期:
- 挂载
当组件实例被创立并插入 DOM 中时,其生命周期调用程序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
留神:
下述生命周期办法行将过期,在新代码中应该防止应用它们:UNSAFE_componentWillMount()
- 更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用程序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
留神:
下述办法行将过期,在新代码中应该防止应用它们:
UNSAFE_componentWillUpdate() UNSAFE_componentWillReceiveProps()
- 卸载
当组件从 DOM 中移除时会调用如下办法:
componentWillUnmount()
事件处理
- 在 React 中你不能通过返回 false 来阻止默认行为。必须明确调用
preventDefault
。
React 本人实现了一套事件机制,本人模仿了事件冒泡和捕捉的过程,采纳了事件代理,批量更新等办法,并且抹平了各个浏览器的兼容性问题。
- React 事件与原生事件的执行程序
- react 的所有事件都
挂载在 document 中
- 当实在 dom 触发后冒泡到 document 后才会对 react 事件进行解决
所以原生的事件会先执行
- 而后执行 react 合成事件
- 最初执行真正在 document 上挂载的事件
- react 事件和原生事件能够混用吗?
react 事件和原生事件最好不要混用。
原生事件中如果执行了 stopPropagation 办法,则会导致其余 react 事件生效。因为 所有元素的事件将无奈冒泡到 document 上
。
this 绑定:你必须审慎看待 JSX 回调函数中的 this,在 JavaScript 中,class 的办法默认不会绑定 this。
办法有三:
- 在结构比函数中绑定一下:
this.handleClick = this.handleClick.bind(this);
- 在类以办法定义事件处理函数时,应用箭头函数:
handleClick = () => {console.log('this is:', this);}
- 间接在回调函数中应用箭头函数:
<button onClick={() => this.handleClick()}>Click me</button>
留神:
[性能优化点]每次渲染 Button 时都会创立不同的回调函数。在大多数状况下,这没什么问题,但如果该 回调函数作为 prop 传入子组件时,这些组件可能会进行额定的从新渲染
。
咱们通常倡议在结构器中绑定或应用 class fields 语法来防止这类性能问题。
组合 vs 继承
React 有非常弱小的组合模式。咱们 举荐应用组合而非继承 来实现组件间的代码重用。Props 和 组合
为你提供了清晰而平安地定制组件外观和行为的灵便形式。
留神:组件能够承受任意 props,包含根本数据类型,React 元素以及函数。
- 应用一个非凡的
{props.children}
来将他们的子组件传递到渲染后果中 - 多数状况下,你可能须要在一个组件中预留出几个“洞”。这种状况下,咱们能够不应用 children,而是自行约定:将所需内容传入 props,并应用相应的 prop,相似于槽
slot
的概念。
Context
Context 提供了一种在 组件之间共享此类值的形式,而不用显式地通过组件树的逐层传递
props。
Context 设计目标是为了共享那些对于一个组件树而言是“全局”的数据,例如以后认证的用户、主题或首选语言。
[代码优化点]
Context 次要利用场景在于很多不同层级的组件须要拜访同样一些的数据。请审慎应用 ,因为这会使得组件的复用性变差。
如果你只是想防止层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
一种无需 context 的解决方案是 将子组件本身传递上来,因此两头组件无需晓得该子组件用到的 props
。
谬误边界
局部 UI 的 JavaScript 谬误不应该导致整个利用解体,为了解决这个问题,React 16 引入了一个新的概念 —— 谬误边界。
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
谬误边界是一种 React 组件,这种组件能够捕捉并打印产生在其子组件树任何地位的 JavaScript 谬误,并且,它会渲染出备用 UI,而不是渲染那些解体了的子组件树。
谬误边界在渲染期间、生命周期办法和整个组件树的构造函数中捕捉谬误。
[代码优化点]
谬误边界无奈捕捉以下场景中产生的谬误:
- 事件处理(理解更多)
- 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
- 服务端渲染
- 它本身抛出来的谬误(并非它的子组件)
Refs 转发
Ref 转发是一项将 ref 主动地通过组件传递到其一子组件的技巧。这个技巧对 高阶组件(也被称为 HOC)特地有用
。
Ref 转发是一个可选个性,其容许某些组件接管 ref,并将其向下传递(换句话说,“转发”它)给子组件。
Fragments
React 中的一个常见模式是一个组件返回多个元素。Fragments 容许你将子列表分组,而无需向 DOM 增加额定节点。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
或者应用短语法:<> </>
高阶组件
定义:高阶组件是 参数为组件,返回值为新组件的函数。
HOC 不会批改传入的组件,也不会应用继承来复制其行为。相同,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用
。
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是 一种基于 React 的组合个性而造成的设计模式
。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。例如:Redux 的 connect
留神:
- 不要在 render 办法中应用 HOC。每次调用 render 函数都会创立一个新的 EnhancedComponent,导致子树每次渲染都会进行卸载,和从新挂载的操作!
- 务必复制静态方法。你能够应用 hoist-non-react-statics 主动拷贝所有非 React 静态方法
- Refs 不会被传递。
与第三方库协同
咱们会 增加一个 ref 到这个根 DOM 元素
。在 componentDidMount 中,咱们可能获取它的援用这样咱们就能够把它传递给 jQuery 插件了。
为了避免 React 在挂载之后去触碰这个 DOM,咱们会从 render() 函数返回一个空的 <div />
。
这个
元素既没有属性也没有子元素,所以 React 没有理由去更新它,使得 jQuery 插件能够自在的治理这部分的 DOM
class SomePlugin extends React.Component {componentDidMount() {this.$el = $(this.el);
this.$el.somePlugin();}
componentWillUnmount() {this.$el.somePlugin('destroy');
}
render() {return <div ref={el => this.el = el} />;
}
}
性能优化
- 部署时应用生产版本,去除一些正告信息
- 虚拟化长列表。” 虚构滚动”技术。这项技术会在无限的工夫内仅渲染无限的内容,并奇迹般地升高从新渲染组件耗费的工夫,以及创立 DOM 节点的数量。
react-window
和react-virtualized
是热门的虚构滚动库。 - 防止调解。你能够通过
笼罩生命周期办法 shouldComponentUpdate 来进行提速
。该办法会在从新渲染前被触发。其默认实现总是返回 true. 如果你晓得在什么状况下你的组件不须要更新,你能够在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。
继承 React.PureComponent 以代替手写 shouldComponentUpdate()。它用以后与之前 props 和 state 的 浅比拟
覆写了 shouldComponentUpdate() 的实现.
shouldComponentUpdate(nextProps, nextState) {return true;}
- 不可变数据的力量。不扭转原来的对象,应用 … 扩大运算符 或 Object.assign 返回新对象。
Diff 算法
- 当比照两颗树时,React 首先比拟两棵树的根节点。
- 当根节点为 不同类型 的元素时,React 会装配原有的树并且建设起新的树。
componentWillUnmount()
->componentWillMount()
->componentDidMount()
- 当比照两个 雷同类型 的 React 元素时,React 会保留 DOM 节点,
仅比对及更新有扭转的属性
。而后子节点递归。 - 子节点递归
在子元素列表开端新增元素时,更新开销比拟小;
如果只是简略的将新增元素插入到表头,那么更新开销会比拟大,不会意识到应该保留前面的,而是会重建每一个子元素。这种状况会带来性能问题。
通过增加 key 来解决。
尽量用雷同的节点类型和稳固可预测的 Key。
Render Prop
render prop 是一个用于告知组件须要渲染什么内容的函数 prop。应用 Props 而非 render。
重要的是要记住,render prop 是因为模式才被称为 render prop,你不肯定要用名为 render 的 prop 来应用这种模式。
将 Render Props 与 React.PureComponent 一起应用时要小心。
如果你在 render 办法里创立函数,那么应用 render prop 会对消应用 React.PureComponent 带来的劣势
。
因为浅比拟 props 的时候总会失去 false,并且在这种状况下每一个 render 对于 render prop 将会生成一个新的值。
Key 的应用形式
react 依据 key 来决定是销毁从新创立组件还是更新组件,准则是:
- key 雷同,组件有所变动,react 会只更新组件对应变动的属性。
- key 不同,组件会销毁之前的组件,将整个组件从新渲染。
应用 index 做 key 存在的问题:
当元素数据源的程序产生扭转时,会从新渲染。而如果应用惟一 ID 作为 key,子组件的值和 key 均未发生变化,只是程序产生扭转,因而 react 只是将他们做了挪动,并未从新渲染。
虚构 DOM
对于是否晋升性能
很多文章说 VitrualDom 能够晋升性能,这一说法实际上是很全面的。
间接操作 DOM 是十分消耗性能的,这一点毋庸置疑。然而 React 应用 VitrualDom 也是无奈防止操作 DOM 的。
如果是 首次渲染,VitrualDom 不具备任何劣势
,甚至它要进行更多的计算,耗费更多的内存。
VitrualDom 的劣势在于 React 的 Diff 算法和批处理策略
,React 在页面更新之前,提前计算好了如何进行更新和渲染 DOM。
实际上,这个计算过程咱们在间接操作 DOM 时,也是能够本人判断和实现的,然而肯定会消耗十分多的精力和工夫,而且往往咱们本人做的是不如 React 好的。
所以,在这个过程中 React 帮忙咱们 ” 晋升了性能 ”。
所以,我更偏向于说,VitrualDom 帮忙咱们进步了开发效率,在 反复渲染时它帮忙咱们计算如何更高效的更新,而不是它比 DOM 操作更快。
跨浏览器兼容
React 基于 VitrualDom 本人实现了一套本人的事件机制,本人模仿了事件冒泡和捕捉的过程,采纳了事件代理,批量更新等办法,抹平了各个浏览器的事件兼容性问题。
实现原理
- React 组件的渲染流程
- 应用 React.createElement 或 JSX 编写 React 组件,实际上所有的 JSX 代码最初都会转换成
React.createElement(...)
,Babel 帮忙咱们实现了这个转换的过程。
- createElement 函数对 key 和 ref 等非凡的
props 进行解决
,并获取 defaultProps 对默认props 进行赋值
,并且对传入的孩子节点进行解决,最终结构成一个 ReactElement 对象
(所谓的虚构 DOM)。 - ReactDOM.render 将生成好的虚构 DOM 渲染到指定容器上,其中
采纳了批处理
、事务等机制
并且对特定浏览器进行了性能优化,最终转换为实在 DOM
- 虚构 DOM 组成
- 避免 XSS:借助 Symbol.for(‘react.element’)
- 批处理和事务机制:setState
- 针对性的性能优化:IE/Edge Fragment
- 事件机制:本人实现了一套事件机制,其将所有绑定在虚构 DOM 上的事件映射到真正的 DOM 事件,并
将所有的事件都代理到 document 上
,本人模仿了事件冒泡和捕捉的过程
,并且进行对立的事件散发。