React初探

react首先是类似一个组件库的js文件,包含view和controller的库。

react组件根据平台本身可以映射成原生控件和web dom。

采用babel的编译工具将jsx转换成js来描述对应的元素。

 

Portal

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:

ReactDOM.createPortal(child, container)

vitrualDom

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象

const element = {
  type: 'h1',
  props: {
    className: 'xxxClass',
    children: 'Hello, world!'
  }
};

createElement函数内部做的操作很简单,将props和子元素进行处理后返回一个ReactElement对象

接下来调用:

      render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback)。 将目标元素插入指定节点container

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()。

React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。

ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM。

与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

如果是首次渲染,VitrualDom不具有任何优势,甚至它要进行更多的计算,消耗更多的内存。

VitrualDom的优势在于React的Diff算法和批处理策略,React在页面更新之前,提前计算好了如何进行更新和渲染DOM。

isValidElement()验证对象是否为 React 元素,返回值为 true 或 false

 

diff算法:

先找到新旧树的前置元素和后置元素,并分别用前置和后置指针指向。

1、如果新旧树前置元素相同,前置指针往后移;

2、如果碰到前置元素不相同,则找后置元素,如果后置元素相同,则后置指针前移。

3、如果前置和后置元素都不相同,则比较旧树的前置元素和新树的后置元素,如果相同,分别挪动旧树的前置指针和新树的后置指针。

4、如果旧树的前置元素和新树的后置元素不相同,则比较旧树的后置元素和新树的前置元素,如果相同,分别挪动旧树的后置指针和新树的前置指针。

5、如果都不相同,使用数组P记录下旧树中前置指针和后置指针之间的元素在新树的下标,如果在旧树中找不到,表示新增,下标为-1。找出P数组中的LIS序列。

     从新树的尾置指针遍历新树。如果新树元素在新树中的下标在LIS中存在,则不移动,如果不存在,则移动

diff算法,只对比同级元素

 

如何防止xss攻击:

React渲染时会把没有$$typeof标识,以及规则校验不通过的组件过滤掉。 防止xss攻击

无状态组件:使用无状态函数构建的组件成为无状态组件,只传入props,context两个参数,不存在state,没有生命周期方法。无状态组件在调用时不会创建新实例,避免了不必要的检查和内存分配。

 

Refs:

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

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

还有一种新的短语法可用于声明它们,但尚未得到所有流行工具的支持。

 

React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。

 

Memo:

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);

 

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;}

   }

       return false;

cloneElement

React.cloneElement: 给指定组件传递props,以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 key和 ref 将被保留。

React.cloneElement(
  element,
  [props],
  [...children]
)

React.children: 获取当前组件的子组件

 

合成事件:

React自己构造了合成事件对象SyntheticEvent,SyntheticEvent 实例将被传递给你的事件处理函数。

这是一个跨浏览器原生事件包装器。 它具有与浏览器原生事件相同的接口,包括stopPropagation() 和 preventDefault() 等等,在所有浏览器中他们工作方式都相同。

如果因为某些原因,当你需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。

SyntheticEvent 是合并而来。这意味着 SyntheticEvent 对象可能会被重用,而且在事件回调函数被调用后,所有的属性都会无效。

合成事件实际上是组件挂载时注册到document上的,事件冒泡到document被监听到之后,会生成一个合成事件对象并分发给对应的handler去处理

       事件捕获:会优先调用结构树最外层的元素上绑定的事件监听器,然后依 次向内调用,一直调用到目标元素上的事件监听器为止。

       事件冒泡:则与事件捕获的表现相反,它会从目标元素向外传播事件,由内而外直到最外层。

 

非受控组件

非受控组件:一个表单组件没有 value props(单选按钮和复选框对应的是 checked prop) 时,就可以称为非受控组件。

      它是一种反模式,它的值不受组件自身的 state 或 props 控制。通常, 需要通过为其添加 ref prop 来访问渲染后的底层 DOM 元素。

 

CSS Modules

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>,

这种形式轻松对应全局和局部;

 

跨级组件通信:

1、

在子组件定义 static contextTypes = {color: PropTypes.string} ,通过this.context.color获取顶层组件的color属性

在顶层组件定义 static childContextTypes = {color: PropTypes.string},  实现方法 getChildContext() {return{color: 'red'}

2、

没有嵌套关系的,那只能通过可以影响全局的一些机制去考虑。import { EventEmitter } from 'events';

在 componentDidMount 事件中,如果组件挂载完成,再订阅事件;当组件卸载的时候,在 componentWillUnmount 事件中取消事件的订阅。

 

mixins

对于广义的 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 这个方法,也就是定义与赋值的区别,定义是 对已有的定义,赋值则是覆盖已有的定义。所以说前者并不会覆盖已有方法,但后者会。

 

HOC:

  属性代理是常见高阶组件的实现方法

  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

 

 

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 合并。

且在 componentWillReceivePropsshouldComponentUpdate 和 componentWillUpdate 中也还是无法获取到更新后的 this.state,

即此 时访问的 this.state 仍然是未更新的数据,需要设置 inst.state = nextState 后才可以,因此 只有在 render 和 componentDidUpdate 中才能获取到更新后的 this.state。

 

在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如 果此时在 componentWillUnmount 中调用 setState,

是不会触发 re-render 的,这是因为所有更新 队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作。

componentWillMountcomponentWillReceivePropscomponentWillUpdate

getDerivedStateFromProps 取代了。主要由于fiber导致上面三个函数会重复调用

 

setstate

React 利用状态队列机制实现了 setState的异步更新,避免频繁地重复更新 state。

1.将setState传入的partialState参数存储在当前组件实例的state暂存队列中。

  当调用 setState 时,实际上会执行 enqueueSetState 方法,_pendingStateQueue 更新队列 对partialState 进行合并操作,

  通过 enqueueUpdate 执行 state 更新。

function enqueueUpdate(component) {

  ensureInjected();

  // 如果不处于批量更新模式
  if (!batchingStrategy.isBatchingUpdates) {

            3.如果未处于批量更新状态,将批量更新状态标识设置为true,用事务再次调用前一步方法,保证当前组件加入到了待更新组件队列中。

    batchingStrategy.batchedUpdates(enqueueUpdate, component);

    return;

  }

  2.判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中。

  // 如果处于批量更新模式,则将该组件保存在 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 {

    4.调用事务的waper方法,遍历待更新组件队列依次执行更新。

    transaction.perform(callback, null, a, b, c, d, e);----事务

  }

},

5.执行生命周期componentWillReceiveProps。

6.将组件的state暂存队列中的state进行合并,获得最终要更新的state对象,并将队列置为空。

7.执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。

8.执行生命周期componentWillUpdate。

9.执行真正的更新,render。

10.执行生命周期componentDidUpdate。

在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 调 用 setState ,又会调用 

shouldComponentUpdate 和 componentWillUpdate 方法,因此造成循环调用,使得浏览器内存占满后崩溃。

 

setTimeout 里面调用setState的时候,把它丢到列队里,并没有去执行,而是先执行的 finally 主进程代码块,等 finally 执行完了, isBatchingUpdates 又变为了 false ,
导致最后去执行队列里的 setState 时候,表现就会和原生事件一样,可以同步拿到最新的state的值。
 
react-router-dom:

重构history类。并作为react.context的value传递给route子组件

hash:

在url中多了以#结尾的hash值,但是赋值前后虽然页面的hash值改变导致页面完整的url发生了改变,但是页面是不会刷新的。

此外,还有一个名为hashchange的事件,可以监听hash的变化,我们可以通过下面两种方式来监听hash的变化:

window.onhashchange=function(event){

   console.log(event);

}

History:

History.back(): 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同

History.go(): 可以跳转到浏览器会话历史中的指定的某一个记录页

History.forward():指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同

History.pushState():pushState可以将给定的数据压入到浏览器会话历史栈中,该方法接收3个参数,对象,title和一串url。pushState后会改变当前页面url,但是不会伴随着刷新

History.replaceState():replaceState将当前的会话页面的url替换成指定的数据,replaceState后也会改变当前页面的url,但是也不会刷新页面。

 BrowserRouter:

用<BrowserRouter> 组件包裹整个App系统后,就是通过html5的history来实现无刷新条件下的前端路由。

如果用history做为路由的基础,那么需要用到的是history.pushState和history.replaceState,在不刷新的情况下可以改变url的地址,

且如果页面发生回退back或者forward时,会触发popstate事件。

监听history.urlChange(),更新context中的location和history的值,以及match属性

Link:

<Link>类似于html中的a标签,此外<Link>在改变url的时候,可以将一些属性传递给匹配成功的Route,供相应的组件渲染的时候使用。

to属性的值也可以是一个对象,该对象可以包含一下几个属性:pathname、seacth、hash和state,其中前3个参数与如何改变url有关,

最后一个state参数是给相应的改变url时,传递一个对象参数。

举例来说: <Link to={{pathname:'/home',search:'?sort=name',hash:'#edit',state:{a:1}}}>Home</Link>

 

class Link extends React.Component {

   handleClick = event => {

  event.preventDefault();

     const { history } = this.context.router;

     const { replace, to } = this.props;

     if (replace) {

       history.replace(replace);

     } else {

      history.push(to);

     }

   }

  };

  render(){

    const { replace, to, innerRef, ...props } = this.props;

     <a {...props} onClick={this.handleClick}/>

  }

}

Route

Route组件也很简单,其props中接受一个最主要的属性path,Route做的事情只有一件:

当url改变的时候,根据context中history的变化,将history.location.pathname属性与自身的path做对比,如果匹配成功,则渲染该组件的componet或者children属性所赋值的那个组件。

 

 

跨域请求的一种解决方案:

const proxy = require('http-proxy-middleware');

module.exports = function(app) {

  app.use(proxy('/api', { target: 'http://localhost:5000/' }));

};

 

suspend和lazy懒加载 

 

 

 

react 性能优化:

网络层的优化,gzip等

 pureComponent

 在shouldUpdate()深浅比较(immutable.js可避免浅比较带来的问题)

 key

 React.Memo来缓存组件

 避免动态创建函数props

 suspend和lazy懒加载

 使用css隐藏和显示组件而不是使用?:来加载和卸载组件

 避免深层次的state数据传递,会让无关中间组件render,可通过context传数据

 组件颗粒化,减少无关的render

 reselector: 接受Redux store state作为参数缓存mapStateToProps的计算结果,避免重复计算

 immutablejs 提升操作数据结构的性能

 

 

posted @ 2019-05-16 15:44  程石亮  阅读(200)  评论(0编辑  收藏  举报