[react] 积累
State
state(状态)更新会被合并.
不要直接修改 state(状态)
唯一可以分配 this.state 的地方是构造函数。
// 错误
//这样将不会重新渲染一个组件:
this.state.comment = 'Hello';
// 正确
this.setState({comment: 'Hello'});
setState一般是异步的
setState 可能是异步或同步更新,不能依赖他们的值计算下一个state(状态)。
// 错误
//可能导致 counter(计数器)更新失败
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
//不用箭头函数
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
为何setState可能是同步更新
- setState 只在合成事件(onClick、onChange)和生命周期函数中是“异步”的,在原生事件(click)和setTimeout 中都是同步的。
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新(render?)之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,可以通过第二个参数
setState(partialState, callback)
中的callback拿到更新后的结果。 - 如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并。
setState 方法与包含在其中的执行是一个很复杂的过程,从 React 最初的版本到现在,也有无数次的修改。它的工作除了要更动 this.state 之外,还要负责触发重新渲染,这里面要经过 React 核心 diff 算法,最终才能决定是否要进行重渲染,以及如何渲染。而且为了批次与效能的理由,多个 setState 呼叫有可能在执行过程中还需要被合并,所以它被设计以延时的来进行执行是相当合理的。
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。
immutable
不应该在setState时对原state数据进行突变(mutate)操作,很可能会导致数据变化但组件没更新的情况.
//有问题的写法
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
//推荐的写法
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
推荐使用immutable.js
获取真实DOM的方式
ref
16.3开始的写法
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focus();
}
render() {
return (
<Input ref={this.textInput} />
);
}
}
<div ref={(x) => this._dom = x}>
</div>
//this._dom 即真实dom
//等效
refDom(node){
this._dom = node
}
<div ref={this.refDom}>
</div>
//this._dom 即真实dom
//另一种写法
//deprecated 新版本被废弃
<div ref="_dom">
</div>
//this.refs._dom 即真实dom
findDOMNode
可以找ref或者e.target,其他没试过
import ReactDOM from 'react-dom';
let dom = ReactDOM.findDOMNode(xx);
优化 redux 的 action
当应用越来越大之后,action 的数量也会大大增加,
为每个 action 对象显式地写上 type 和 data 或者其它属性会造成大量的代码冗余,
这一块是完全可以优化的。
一个最简单的 actionCreator
function actionCreator(type){
return function(data){
return {
type: type,
data: data
}
}
}
var foo = actionCreator('FOO');
foo(123); // {type: 'FOO', data: 123}
或者安装redux-actions
封装集合渲染为独立组件
这一点在循环渲染集合组件时尤其重要,
React 在渲染大型集合是性能十分糟糕,
原因是 React 会在每次更新中全部重新渲染,
因此建议将渲染集合的部分装为独立的组件渲染
// Bad
class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>
</div>)
}
}
// Good
// 当 user.name 更新时,列表不会重新渲染
class MyComponent extends Component {
render() {
const {todos, user} = this.props;
return (<div>
{user.name}
<TodosView todos={todos} />
</div>)
}
}
class TodosView extends Component {
render() {
const {todos} = this.props;
return (<ul>
{todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
</ul>)
}
}
尽早绑定方法
在 render() 中绑定的方法应该尽早声明,而不是在渲染时定义
// Bad
render() {
return <MyWidget onClick={() => { alert(this.state.text) }} />
}
// Good
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert(this.state.text);
}
render() {
return <MyWidget onClick={this.handleClick} />
}
不变组件禁用更新
对于不需要更新的组件,
可以在 shouldComponentUpdate() 中 return false,
或者使用 Stateless Component
// Bad
class Logo extends Component {
render() {
return <div><img src='logo.png' /></div>;
}
}
// Good
class Logo extends Component {
shouldComponentUpdate() {
return false;
}
render() {
return <div><img src='logo.png' /></div>;
}
}
// or Stateless Component
const Logo = () => <div><img src='logo.png' /></div>;
react中的数据流
尽量不要将props/store里的数据存入state里进行操作,很容易出现props/store里的数据更新后state的数据没有变化的情况,在层级较多的情况,componentWillReceiveProps也未必能拯救你。
一致性比较
不同类型的两个元素将会产生不同的树。
开发人员可以使用一个 key prop 来指示在不同的渲染中那个那些元素可以保持稳定。
Diffing算法
当比较不同的两个树,React 首先比较两个根元素。根据根元素的类型不同,它有不同的行为。
元素类型不相同
无论什么时候,当根元素类型不同时,React 将会销毁原先的树并重写构建新的树。从 <a>
到 <img>
,或者从 <Article>
到 <Comment>
,从 <Button>
到 <div>
– 这些都将导致全部重新构建。
当销毁原先的树时,之前的 DOM 节点将销毁。实例组件执行 componentWillUnmount()
。当构建新的一个树,新的 DOM 节点将会插入 DOM 中。组件将会执行 componentWillMount()
以及 componentDidMount()
。与之前旧的树相关的 state 都会丢失。
DOM元素类型相同
当比较两个相同类型的 React DOM 元素时,React 检查它们的属性(attributes),保留相同的底层 DOM 节点,只更新发生改变的属性(attributes)。
在处理完当前 DOM 节点后,React 会递归处理子节点。
相同类型的组件
当一个组件更新的时候,组件实例保持不变,以便在渲染中保持state。
React会更新组件实例的属性来匹配新的元素,并在元素实例上调用 componentWillReceiveProps()
和 componentWillUpdate()
。
接下来,render()
方法会被调用并且diff算法对上一次的结果和新的结果进行递归。
子元素递归
默认情况下,当递归一个 DOM 节点的子节点时,React 只需同时遍历所有的孩子基点,同时当它们不同时生成一个改变(mutation)。
Keys
React 支持一个 key 属性(attributes)。当子节点有了 key ,React 使用这个 key 去比较原来的树的子节点和之后树的子节点。
key 需要在它的兄弟节点中是唯一的就可以了,不需要是全局唯一。
可以将数组中的索引作为 key 。但是如果存在重新排序,性能将会很差,最好使用其他值作为key。
注意
diff算法不会尝试匹配不同节点类型的子树。
keys 应该是稳定的、可预测的并且是唯一的。不稳定的 key (类似于 Math.random() 函数的结果)可能会产生非常多的组件实例并且 DOM 节点也会非必要性的重新创建。这将会造成极大的性能损失和组件内state的丢失。
react diff算法
tree diff
只会比较同级节点,一旦发现子节点不存在,会删除该节点与该节点下的子节点,所以只会遍历一次。
component diff
先判断是不是同类,是同类继续往下判断,允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
不是则判为dirty component,建新的component,包括component下所有子节点。
element diff
比较同层节点,插入,移动,删除,添加唯一key。
层级比较
对于不同节点类型,React才用层级比较。facebook做了一个优化,让diff的复杂度为O(n),用传统方法则是O(n^3)。
React只会对相同颜色方框内的DOM节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。如果有,则添加。
会有下面的一系列更新
C will unmount.
C is created.
B is updated.
A is updated.
C did mount.
D is updated.
R is updated.
列表节点的diff
当list指定了key属性.
react利用key来识别组件,它是一种身份标识标识,就像我们的身份证用来辨识一个人一样。每个key对应一个组件,相同的key react认为是同一个组件。
key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
key值不同,则react先销毁该组件(有状态组件的componentWillUnmount会执行),然后重新创建该组件(有状态组件的)
为什么这么做呢?当对列表进行一系列操作(增删改查)的时候,dom结构的变化更少,根据key来。
当没有提供key,则会按照层级比较的方式更新。
在层级比较的情况下,会有如下更新
B will unmount.
C will unmount.
C is created.
B is created.
C did mount.
B did mount.
A is updated.
R is updated.
而指定了key的比较则是如下更新
C is updated.
B is updated.
A is updated.
R is updated.
redux
redux是纯函数的,"状态转移管理"库。
它是无状态的,状态是在你的程序里的,你自己维持状态,它只是给你提供了一个状态转移的统一方式。
异步的reducer会破坏了它的“纯度”,因为异步是不确定的,先发不一定先至,这会破坏reducer的“可回放性”。
解决了组件间数据沟通的问题。
优点:
- 数据与组件解耦,并且是单向数据流,容易单元测试。
- 数据清晰,更利于团队管理,更不容易出错。
缺点:
- 步骤繁琐。
- 解决异步问题需要引入额外的中间件(例如redux-thunk)。
- 多人协作开发,如果没有事先约定action type,有可能会造成冲突。组件页面多了以后,也有可能会出现state树过于复杂的问题。
- 抛弃了state不利于组件复用。
改进方向:
- 使用例如redux-arena这样的模块,限制action和reducer的作用域。
- 合理分离分离容器组件和展示组件。组件内部自己管理自己的state,不使用redux,增加复用性;组件间交流使用,简化使用,降低store复杂度。
- 将 Redux 进一步封装以达到简化的目的,可以一定程度上针对自身的业务场景,例如:dva、refect、refast 等等。
- 采用 observable 的方案,例如:MobX、dob 等等。
- 不使用第三方模块,使用react高阶组件+context方案代替。
//可以尝试一种高阶组件+无状态组件+context的状态管理方案
细节优化
- PropTypes 可以用 TypeScript 替代.
- 使用immutable.
- 使用如reselect 优化mapStateToProps 里的selector.
Fiber
React Fiber主要是为了解决数据量大时,react的script计算部分太过复杂导致渲染性能下降的问题.
方案是不同于浏览器的Stack Reconcile自己内部实现了Fiber Reconcile,另一种执行机制.
ReactFiber 会将整个更新任务分成若干个小的更新任务,然后设置一些任务默认的优先级。每执行完一个小任务之后,会释放主线程。
深入JSX
JSX 只是为 React.createElement(component, props, ...children) 函数提供的语法糖。
因为 JSX 被编译为 React.createElement 的调用,所以 React 库必须在你 JSX 代码的作用域中。
如果实在懒得一直写,可以通过其他方式全局引入.
自定义组件必须首字母大写.
JSX 类型不能是表达式,但是可以将表达式赋值给一个以大写字母开头的变量。
props(属性) 默认为 “true”,但是不推荐这么用.
false,null,undefined,和 true 都是有效的的 children(子元素),但是并不会被渲染.
需要注意的是“falsy”值,例如数值 0 ,仍然会被 React 渲染。(估计是因为都被识别为字符串的原因).
props.children 的值可以是回调函数
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
Refs 和 DOM
何时使用 Refs
有一些正好使用 refs 的场景:
- 处理focus、文本选择或者媒体播放
- 触发强制动画
- 集成第三方DOM库
如果可以通过声明式实现,就尽量避免使用 refs 。
例如,相比于在 Dialog 组件中暴露 open() 和 close() 方法,最好传递 isOpen 属性。
当 ref 属性用于类(class)声明的自定义组件时,ref 回调函数收到的参数是装载(mounted)的组件实例。
不能在函数式组件外部上使用 ref 属性,但是可以在内部使用.
对父组件暴露 DOM 节点
从父组件访问子节点的 DOM 节点。通常不建议这样做,因为它会破坏组件的封装,但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然可以向子组件添加 ref,但这不是一个理想的解决方案,因为只能获取组件实例而不是 DOM 节点。并且,它还在函数式组件上无效。
相反,在这种情况下,可以在子节点上暴露一个特殊的属性。子节点将会获得一个函数属性,并将其作为 ref 属性附加到 DOM 节点。这允许父代通过中间件将 ref 回调给子代的 DOM 节点。
适用于类组件和函数式组件。
Parent 将它的 ref 回调作为一个特殊的 inputRef 传递给 CustomTextInput,然后 CustomTextInput 通过 ref 属性将其传递给 <input>
。最终,Parent 中的 this.inputElement 将被设置为与 CustomTextInput 中的 <input>
元素相对应的 DOM 节点。
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。如果无法完全控制子组件,最后的办法是使用 findDOMNode()
,但是不推荐这样做。
string类型的 refs 存在问题,已经过时了,可能会在未来的版本是移除。建议用回调函数的方式代替。
片段(fragments)
React 中一个常见模式是为一个组件返回多个元素。
片段(fragments) 可以将子元素列表添加到一个分组中,并且不会在DOM中增加额外节点。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
key 是唯一可以传递给 Fragment 的属性。
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,将会触发一个key警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
这种写法可能还没被完全支持
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
插槽(Portals)
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 片段(fragment)。
第二个参数(container)则是一个 DOM 元素。
有时候将子元素插入到 DOM 节点的其他位置会有用的:
render() {
// React *不* 会创建一个新的 div。 它把 children 渲染到 `domNode` 中。
// `domNode` 可以是任何有效的 DOM 节点,不管它在 DOM 中的位置。
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
对于 portal 的一个典型用例是当父组件有 overflow: hidden 或 z-index 样式,但需要子组件能够在视觉上 “跳出(break out)” 其容器。
例如,对话框、hovercards以及提示框.
通过 Portals 进行事件冒泡
尽管 portal 可以被放置在 DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。如上下文特性依然能够如之前一样正确地工作,无论其子节点是否是 portal,由于 portal 仍存在于 React tree
中,而不用考虑其在 DOM tree
中的位置。
这包含事件冒泡。一个从 portal 内部会触发的事件会一直冒泡至包含 React tree 的祖先。
假设如下 HTML 结构:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
在 #app-root
里的 Parent 组件能够捕获到未被捕获的从兄弟节点 #modal-root
冒泡上来的事件。
在父组件里捕获一个来自 portal 的事件冒泡能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。
例如,若渲染一个 <Modal />
组件,父组件能够捕获其事件而无论其是否采用 portal 实现。
// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// This will fire when the button in Child is clicked,
// updating Parent's state, even though button
// is not direct descendant in the DOM.
this.setState(prevState => ({
clicks: prevState.clicks + 1
}));
}
render() {
return (
<div onClick={this.handleClick}>
<p>Number of clicks: {this.state.clicks}</p>
<p>
Open up the browser DevTools
to observe that the button
is not a child of the div
with the onClick handler.
</p>
<Modal>
<Child />
</Modal>
</div>
);
}
}
function Child() {
// The click event on this button will bubble up to parent,
// because there is no 'onClick' attribute defined
return (
<div className="modal">
<button>Click</button>
</div>
);
}
ReactDOM.render(<Parent />, appRoot);
兼容性写法
const isReact16 = ReactDOM.createPortal !== undefined;
const getCreatePortal = () =>
isReact16
? ReactDOM.createPortal
: ReactDOM.unstable_renderSubtreeIntoContainer;
错误边界(Error Boundaries)
错误边界是 React 组件,它可以在子组件树的任何位置捕获 JavaScript 错误,记录这些错误,并显示一个备用 UI,而不是使整个组件树崩溃。 错误边界(Error Boundaries) 在渲染,生命周期方法以及整个组件树下的构造函数中捕获错误。
错误边界无法捕获如下错误:
- 事件处理
- 异步代码 (例如 setTimeout 或 requestAnimationFrame 回调函数)
- 服务端渲染
- 错误边界自身抛出来的错误 (而不是其子组件)
如果一个类组件定义了一个名为 componentDidCatch(error, info)
的新生命周期方法,它将成为一个错误边界:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后可以像一个普通的组件一样使用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
componentDidCatch()
方法机制类似于 JavaScript catch {}
,但是针对组件。仅有类组件可以成为错误边界。
error 是被抛出的错误。
info 是一个含有 componentStack 属性的对象。这一属性包含了错误期间关于组件的堆栈信息。
componentDidCatch(error, info) {
/* Example stack information:
in ComponentThatThrows (created by App)
in ErrorBoundary (created by App)
in div (created by App)
in App
*/
logComponentStackToMyService(info.componentStack);
}
自 React 16 开始,任何未被错误边界捕获的错误将会卸载整个 React 组件树。
React 16 会将渲染期间所有在开发环境下的发生的错误打印到控制台,即使应用程序意外的将其掩盖。除了错误信息和 JavaScript 栈外,其还提供了组件栈追踪。现在可以准确地查看发生在组件树内的错误信息.
也可以在组件堆栈中查看文件名和行数。
但是需要加入插件babel-plugin-transform-react-jsx-source 这个好像是babel内置了?
高阶组件(Higher-Order Components)
高阶组件可用在有功能逻辑相似时的组件复用.
不要在render函数中使用高阶组件
render() {
// 每一次render函数调用都会创建一个新的EnhancedComponent实例
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 每一次都会使子对象树完全被卸载或移除
return <EnhancedComponent />;
}
静态方法必须复制
当将一个组件应用于高阶组件式,虽然原有的组件被容器组件所包裹,但这这新的组件没有之前组件的静态函数。
// 定义静态方法
WrappedComponent.staticMethod = function() {/*...*/}
// 使用高阶组件
const EnhancedComponent = enhance(WrappedComponent);
// 增强型组件没有静态方法
typeof EnhancedComponent.staticMethod === 'undefined' // true
可以使用hoist-non-react-statics来自动复制非React的静态方法。
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
另一个有效的方法是将静态方法与组件本身相分离:
// 替代……
MyComponent.someFunction = someFunction;
export default MyComponent;
// ……分别导出……
export { someFunction };
// ……在要使用的组件中导入
import MyComponent, { someFunction } from './MyComponent.js';
Refs不会被传递
尽管惯例是高阶组件会给被包裹组件传递所有的属性(props),但是不会传递refs。因为ref不是一个属性,就像key一样,它是由React特殊处理的。如果给高阶组件产生的组件的元素添加ref,ref引用的是外层的容器组件的实例,而不是被包裹的组件。
也就是说refs有时候是必要的,否则React也不会提供refs。
function Field({ inputRef, ...rest }) {
return <input ref={inputRef} {...rest} />;
}
// 在高阶组件中增强Field组件
const EnhancedField = enhance(Field);
// 组件的render函数中……
<EnhancedField
inputRef={(inputEl) => {
// 该回调函数被作为常规的props属性传递
this.inputEl = inputEl
}}
/>
// 现在可以愉快的调用控制函数了
this.inputEl.focus();
virtual dom
- 只要数据发生改变,就会重新生成一个完整的Virtual DOM。
- 重新计算比较出新的和之前的Virtual DOM的差异。
- 更新真实DOM中真正发生改变的部分,就像是给DOM打了个补丁。
React弱于初始化渲染,强于管理页面的更新,在页面有复杂变动时,是比改变原生dom快的.
Batching 或者 Diff, 说到底,都是为了尽量减少对慢速DOM的调用。
数据模型的每一次改变都会触发Virtual DOM的重新生成,这就是React和其他框架的不同之处。
其他框架会检测文档的变化,只更新必要的部分。
Virtual DOM通常占用更少的内存,因为它不需要在内存中常驻观察者。
但是,每次改动发生后都比较两个完整的Virtual DOM是低效的。复杂的UI对于CPU的要求也很高。
鉴于这个原因,React开发者要主动决定需要渲染的内容。
如果你知道某个行为不会影响对应的组件,你应当告知React不要去分析组件的变动--这可以节省大量的资源,显著地提升应用的性能。
- 初始渲染:Virtual DOM > 脏检查 >= 依赖收集
- 小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) > Virtual DOM 无优化
- 大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化
杂
class变为className
for变为htmlFor
更多
html标签自定义属性需要data-
前缀
自定义标签支持任意自定义属性
JSX命名空间的写法
<XUI.Button label="hello"/>
React合成事件的事件类型是javascript原生事件类型的一个子集。
React合成事件系统的委托机制,在合成事件内部仅仅对最外层的容器进行了绑定,并且依赖事件冒泡机制完成了委派。
解决该问题的方案:
- 不要将合成事件与原生事件混用。
- 通过e.target判断来避免。
React中的style大部分可以省略px。
更改组件的key可以让组件重载(组件变了)。
静态属性和不写bind(this)都需要transform-class-properties插件
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
// 这里使用箭头绑定方法:
handleClick = () => {
//...
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
不受控组件一般是指input这些通过ref取值.
旧版的context更新数据不可靠,新版还未知.
那些aria-*
HTML属性主要是为了可访问性(Accessibility).
动态 import()
import("./math").then(math => {
console.log(math.add(16, 26));
});
在大多数情况下,可以不用手写 shouldComponentUpdate()
比较逻辑 ,而是从 React.PureComponent
继承。
Component
的 shouldComponentUpdate()
不(自动)进行处理,始终返回true.
PureComponent
的shouldComponentUpdate()
默认进行浅比较,但对于比较复杂的数据依然无能为力.
可以考虑和immutable.js
配合使用.
有时无法更新可以考虑粗暴的使用forceUpdate().
componentDidMount
和componentDidUpdate
中DOM被真正加入,也就是可以在这2个生命周期中获取真实DOM
在React中使用DOM原生事件时,一定要在组件卸载时手动移除,否则很可能会出现内存泄露的问题。
props也可以传递组件
生命周期里可以用async/await
// 下面主要是项目中按需加载echartsJs(本地文件)
// ...
componentDidMount = async () => {
this.echartsInstance = await import("echarts");
// ...
}
// update
componentDidUpdate = async () => {
this.echartsInstance = await import("echarts");
// ...
}
// ...
// 下面主要是项目中异步加载百度地图脚本(链接),然后做了些异步读取数据
componentWillReceiveProps = async (nextprops) => {
const { dis } = this.state;
if (!!dis || !!nextprops.status) return;
try {
await this.loadScript();
const pos = await this.getPos();
const dis = await this.getDis(pos);
this.setState({
dis
});
}catch(e){
console.log(e);
}
}
JSX 只是为 React.createElement(component, props, ...children)
函数提供的语法糖。
因为 JSX 被编译为 React.createElement
的调用,所以 React 库必须在你 JSX 代码的作用域中。
如果实在懒得一直写,可以通过其他方式全局引入.
自定义组件必须首字母大写.
JSX 类型不能是表达式,但是可以将表达式赋值给一个以大写字母开头的变量。
props(属性) 默认为 “true”,但是不推荐这么用.
props.children 的值可以是回调函数
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
// 阻止合成事件的冒泡
e.stopPropagation();
// 阻止与原生事件的冒泡
e.nativeEvent.stopImmediatePropagation();
// 通过e.target判断阻止冒泡
...
生成自定义组件
//1
newAComponent() {
return <AComponent />;
}
newBComponent() {
return <BComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
//2
var MyComponent = Components[type + "Component"];
return <MyComponent />;
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
一般来说在constructor里进行同步的setState是无效的