react首先是类似一个组件库的js文件,包含view和controller的库。
react组件根据平台本身可以映射成原生控件和web dom。
采用babel的编译工具将jsx转换成js来描述对应的元素。
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
一个 portal 的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:
ReactDOM.createPortal(child, container)
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
React.createElement()
会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:
这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。
const element = {
type: 'h1', props: { className: 'xxxClass', children: 'Hello, world!' } };
无状态组件:使用无状态函数构建的组件成为无状态组件,只传入props,context两个参数,不存在state,没有生命周期方法。无状态组件在调用时不会创建新实例,避免了不必要的检查和内存分配。
FancyButton
使用 React.forwardRef
来获取传递给它的 ref
,然后转发到它渲染的 DOM button
:
const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} </button> )); // 你可以直接获取 DOM button 的 ref: const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>;
Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
还有一种新的短语法可用于声明它们,但尚未得到所有流行工具的支持。
React.PureComponent
来代替手写 shouldComponentUpdate
。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。
React.memo
为高阶组件。它与 React.PureComponent
非常相似,但它适用于函数组件,但不适用于 class 组件。
如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。
与 class 组件中 shouldComponentUpdate()
方法不同的是,如果 props 相等,areEqual
会返回 true
;如果 props 不相等,则返回 false
。这与 shouldComponentUpdate
方法的返回值相反。
function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 如果把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 否则返回 false */ } export default React.memo(MyComponent, areEqual);
isValidElement()
验证对象是否为 React 元素,返回值为 true
或 false
。
react-dom:findDOMNode(ReactComponent) 获取真正的DOM元素,返回该react组件对应的DOM节点。
react-dom:unstable_renderSubtreeIntoContainer: ReactMount._renderSubtreeIntoContainer(parentComponent,
nextElement, container, callback)。将目标元素插入指定节点container
render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback)。 两者的区别在于是否传入父节点。
React.cloneElement: 给指定组件传递props,以 element
元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 key
和 ref
将被保留。
React.cloneElement(
element,
[props],
[...children]
)
React.children: 获取当前组件的子组件
合成事件:
事件委派:React并不会把事件处理函数直接绑定到 真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监 听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是 在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器 处理,然后在映射里找到真正的事件处理函数并调用。
自动绑定:React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。或者利用箭头函数自动获取当前this。
事件捕获:会优先调用结构树最外层的元素上绑定的事件监听器,然后依 次向内调用,一直调用到目标元素上的事件监听器为止。
事件冒泡:则与事件捕获的表现相反,它会从目标元素向外传播事件,由内而外直到最外层。
React的合成事件则并没有实现事件捕获,仅仅支持了事件冒泡机制。
React 事件是天生的事件代理,看起来事件散落在元素上,其实 React 仅仅在 根 元素绑定 事件,所有事件都通过事件代理响应。
非受控组件:一个表单组件没有 value props(单选按钮和复选框对应的是 checked prop) 时,就可以称为非受控组件。
它是一种反模式,它的值不受组件自身的 state 或 props 控制。通常, 需要通过为其添加 ref prop 来访问渲染后的底层 DOM 元素。
classnames样式库:classNames({ 'btn': true, 'btn-pressed': this.state.isPressed, 'btn-over': !this.state.isPressed && this.state.isHovered,});
CSS Modules: 能最大化地结合现有 CSS 生态和 JavaScript 模块化能力,其 API 非常简洁。 发布时依旧编译出单独的 JavaScript 和 CSS 文件。
现在,webpack css-loader 内置 CSS Modules 功能。
启用 CSS Modules 的代码如下:
// webpack.config.js
css?modules&localIdentName=[name]__[local]-[hash:base64:5]
加上 modules 即为启用,其中 localIdentName 是设置生成样式的命名规则。
使用了 CSS Modules 后,就相当于给每个 class 名外加了 :local,以此来实现样式的局部化。如果我们想切换到全局模式,可以使用 :global 包裹
对于样式复用,CSS Modules 只提供了唯一的方式来处理——composes 组合。
/* components/Button.css */ .base { /* 所有通用的样式 */ }
/* settings.css */.primary-color { color: #f40; }
.primary {composes: base; composes: $primary-color from './settings.css'; /* primary 其他样式 */}
如果不想频繁地输入 styles.**,可以使用 react-css-modules 库。它通过高阶组件的形式来 避免重复输入 styles.**。可以这么写---styleName="root"
使用 CSS Modules,容易使用 :global 去解决特殊情况,使用 react-css-modules 可写成 <div className="global-css" styleName="local-module"></div>,
这种形式轻松对应全局和局部;
跨级组件通信:
在子组件定义 static contextTypes = {color: PropTypes.string} ,通过this.context.color获取顶层组件的color属性
在顶层组件定义 static childContextTypes = {color: PropTypes.string}, 实现方法 getChildContext() {return{color: 'red'}
没有嵌套关系的,那只能通过可以影响全局的一些机制去考虑。import { EventEmitter } from 'events';
在 componentDidMount 事件中,如果组件挂载完成,再订阅事件;当组件卸载的时候,在 componentWillUnmount 事件中取消事件的订阅。
对于广义的 mixin 方法,就是用赋值的方式将 mixin 对象里的方法都挂载到原对象上,来实 现对对象的混入。
React 在使用 createClass 构建组件时提供了 mixin 属性,mixins: ['xxx','xxx'],
在不同的 mixin 里实现两个名字一样的普通方法,这会造成冲突。因此, 在 React 中是不允许出现重名普通方法的 mixin。
如果是 React 生命周期定义的方法,则会将各个模块的生命周期方法叠加在一起顺序执行。
使用我们推荐的 ES6 classes 形式构建组件时,它并不支持 mixin。
对于实现 mixin 方法来说,这就没什么不一样了。但既然讲到了语法糖,就来讲讲另一个语 法糖 decorator,正巧可以用来实现 class 上的 mixin。
core-decorators 库为开发者提供了一些实用的 decorator,其中实现了我们正想要的 @mixin。 下面解读一下其核心实现:
function handleClass(target, mixins) {
if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); }
for (let i = 0, l = mixins.length; i < l; i++) {
// 获取 mixins 的 attributes 对象
const descs = getOwnPropertyDescriptors(mixins[i]);
// 批量定义 mixins 的 attributes 对象
for (const key in descs) {
if (!(key in target.prototype)){
defineProperty(target.prototype, key, descs[key]);
}
}
}
}
源代码十分简单,它将每一个 mixin 对象的方法都叠加到 target 对象的原型上 以达到 mixin 的目的。这样,就可以用 @mixin 来做多个重用模块的叠加了。
这里用了getOwnPropertyDescriptor 和 defineProperty 这两个方法,
好处在于 defineProperty 这个方法,也就是定义与赋值的区别,定义是 对已有的定义,赋值则是覆盖已有的定义。所以说前者并不会覆盖已有方法,但后者会。
高阶组件:
属性代理是常见高阶组件的实现方法
const MyContainer = (WrappedComponent) =>
class extends Component {---这里将Component替换成WrappedComponent就实现了反向继承,除了一些静态方法,包括生命周期,state,各种function,我们都可以得到。
我们同时可以以此进行hijack(劫持),也就是控制它的render函数。在render()中调用superRender(),然后通过在外层嵌套的方式改变原有渲染
handleClick = () => {console.log('clicked');}
render() {
const otherProps = {handleClick:this.handleClick}
return <WrappedComponent {...this.props} ref={instanceComponent => this.instanceComponent = instanceComponent}/>;
}
}
export default MyContainer//--->这是一个hoc组件
class MyComponent extends Component { ... }
export default MyContainer(MyComponent);
高阶组件可以看做是装饰器模式(Decorator Pattern)在React的实现。即允许向一个现有的对象添加新的功能,同时又不改变其结构,属于包装模式(Wrapper Pattern)的一种
ES7中添加了一个decorator的属性,使用@符表示,上面一行可以改写成@MyContainer
可以在hoc高阶组件中自定义事件,并通过props传递下去,在hoc高阶组件中使用ref,获取当前被包含组件的引用ref
Immutable
React 做性能优化时最常用的就是 shouldComponentUpdate 方法,但它默 认返回 true,即始终会执行 render 方法,
然后做 Virtual DOM 比较,并得出是否需要做真实 DOM的更新,这里往往会带来很多没必要的渲染。我们也可以在 shouldComponentUpdate
中使用深拷贝和深比较来避免无必要的 render, 但深拷贝和深比较一般都是非常昂贵的选择。
Immutable.js则提供了简洁、高效的判断数据是否变化的方法,只需 === 和 is 比较就能知 道是否需要执行 render,而这个操作几乎零成本,所以可以极大提高性能。
for (const key in nextProps) {
if (nextProps.hasOwnProperty(key) &&
!is(thisProps[key], nextProps[key])) { return true;}
}
react生命周期:
主要通过 3 个阶段进行管理—— MOUNTING、RECEIVE_PROPS 和 UNMOUNTING。
当首次挂载组件时,按顺序执行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。
当重新挂载组件时,此时按顺序执行 getInitialState、componentWillMount、render 和componentDidMount,但并不执行 getDefaultProps。
当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
当卸载组件时,执行 componentWillUnmount。
creatClass是创建自定义组件的入口方法,负责管理生命周期中的 getDefaultProps。该方法在整个生命周期中只执行一次,这样所有实例初始化的 props 将会被共享。
由于 getDefaultProps 是通过构造函数进行管理的,所以也是整个生命周期中最先开始执行 的。
在 componentWillMount 中调用 setState 方法,是不会触发 re-render的,而是会进行 state 合并,且 inst.state = this._processPendingState (inst.props, inst.context) 是在 componentWillMount 之后执行的,
因此 componentWillMount 中 的 this.state 并不是最新的,在 render 中才可以获取更新后的 this.state。
React 是利用更新队列 this._pendingStateQueue 以及更新状态 this._pendingReplace State 和 this._pendingForceUpdate 来实现 setState 的异步更新机制。
在 componentWillReceiveProps 中调 用 setState,是不会触发 re-render 的,而是会进行 state 合并。
且在 componentWillReceiveProps、shouldComponentUpdate 和 componentWillUpdate 中也还是无法获取到更新后的 this.state,
即此 时访问的 this.state 仍然是未更新的数据,需要设置 inst.state = nextState 后才可以,因此 只有在 render 和 componentDidUpdate 中才能获取到更新后的 this.state。
在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如 果此时在 componentWillUnmount 中调用 setState,
是不会触发 re-render 的,这是因为所有更新 队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作。
react中的setstate:
React 利用状态队列机制实现了 setState的异步更新,避免频繁地重复更新 state。
当调用 setState 时,实际上会执行 enqueueSetState 方法,并对 partialState 以及_pendingStateQueue 更新队列进行合并操作,
最终通过 enqueueUpdate 执行 state 更新。
而 performUpdateIfNecessary 方法会获取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,
并调用 receiveComponent 和 updateComponent 方法进行组件更新。
在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 调 用 setState , 此 时this._pendingStateQueue != null,
则 performUpdateIfNecessary 方法就会调用 updateComponent方法进行组件更新,但 updateComponent 方法又会调用
shouldComponentUpdate 和 componentWillUpdate 方法,因此造成循环调用,使得浏览器内存占满后崩溃。
setState 最终是通过 enqueueUpdate 执行 state 更新。
function enqueueUpdate(component) {
ensureInjected();
// 如果不处于批量更新模式
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
dirtyComponents.push(component);
}
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);----事务
}
},