dlrandy / note-issues

2 stars 0 forks source link

react 16相关新特性 #63

Open dlrandy opened 6 years ago

dlrandy commented 6 years ago

使用ErrorBounaries进行错误处理 UI里的js错误不该破坏掉整个app。ErrorBoundaries就是一个react组件,来catch子组件树里的jserror的,它可以记录errors,显示fallbackUI,而不会让整个的组件树崩溃

React 16加了一个新的生命周期钩子componentDidCatch(error, componentStack)来处理封装的组件抛出来的errors,而不会卸载整个的app.这个钩子在production环境的时候,可以用来给server发送error报告。

componentDidCatch的几个特点: 1、它捕获子组件树上任意位置的jserror,记录Error,当组件崩溃的时候渲染fallback UI 2、可以封装在一个独立的组件进行复用 3、因为Errorboundaries作用于深度内嵌的结构,所以最好把他放在有意义的位置,而不是每一级 4、任何的功能组件或者类组件的render方法,构造器方法以及生命周期方法里。注意任何的Eventhandler方法抛出的错误不会被钩子捕获,除非它是在setState的updater函数里。

总结一下就是react组件的方法报错都好使,事件处理句柄只能是setState的方法里有错误的时候好使

let sendToErrorReporting = (error, info) => {
 console.log("error, info ", error, info);
}

class MyErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }

  componentDidCatch(error, info) {
    this.setState(state => ({
      ...state,
      hasError: true,
    }));
    sendToErrorReporting(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <div>Sorry, soething went wrong.</div>;
    } else {
      return this.props.children;
    } 
  }

}

class Profile extends React.Component {
  onClick = () => {
    this.setState(state => {
      throw new Error('Oh moooo');
      return {...state};
    });
  }
  render() {
    return (
      <div onClick={this.onClick}>
      Name: {this.props.user.name}
      </div> 
    );
  }
}

class App extends React.component {
  constructor(props) {
    super(props);
    this.state = {
      user: {
        name: 'Chris'
      }
    };
  }

  updateUser = () => {
    this.setState(state => ({
      ...state, user: null
    }));
  }

  render(){
    return (
      <div>
        <MyErrorBoundary>
        <Profile user={this.state.user} />
        <button onClick={this.updateUser}>Update</button>
        </MyErrorBoundary>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
dlrandy commented 6 years ago

渲染多个元素不需要封装在组件的一个公共父元素里了 React16之前要求render方法只能返回一个react元素。16以后是可以返回一个元素 数组的。列表的key不能省略,但是多个组件返回的是同一类的列表元素,key是可以相同的

const data = [
  [14, 24, 125, 182], 
  [10, 122, 220, 310], 
  [0, 19, 38, 50]
];

const Aux = props => {
  return props.children;
};

const Fruits = () => [
  <li key="1">Apple</li>
  <li key="2">Orange</li>
  <li key="3">Banana</li>
];

class MoreFruits extends React.Component {
  render(){
    return [
      <li key="1">Strawberry</li>,
      <li key="2">Blueberry</li>
    ]
  }
}

class App extends React.Component {
  render(){
    return (
      <div>
        <ul>
          <li>Peach</li>
          <li>Ananas</li>
          <Fruits />
          <MoreFruits />
        </ul>
        <table>
          <tbody>
            <RawRows data={data} />
            <StatsRows data={data} />
          </tbody>
        </table>
      </div>
    );
  }
}

let idCounter = 0;
const uniqueId = () => ++idCounter + '';

const convertRowsToColumns = data => (
 data[0].map( (col, i) => data.map(row => row[i]))
);
const sumPerColumn = data => (
  convertRowsToColumns(data).map(cols => cols.reduce((sum, col) => {
    return sum + col;
  }, 0))
);

const trendInPercent = data =>
  sumPerColumn(data).map((value, index, list) => {
    if (index === 0) {
      return 0;
    }
    return (100 / list[index - 1] * value - 100).toFixed(0);
  });

const RawRows = props =>
  props.data.map(row => (
    <tr key={uniqueId()}>
      {row.map(item => <td key={uniqueId()}>{item}</td>)}
    </tr>
  ));

const SumRow = props => (
  <tr key="1" style={{ borderTop: '2px solid #333' }}>
    {sumPerColumn(props.data).map(value => <td key={uniqueId()}>{value}</td>)}
  </tr>
);

const TrendRow = props => (
  <tr key="2">
    {trendInPercent(props.data).map(value => (
      <td key={uniqueId()}>{value}%</td>
    ))}
  </tr>
);

const StatsRows = props => (
  <Aux>
    <SumRow data={props.data} />
    <TrendRow data={props.data} />
  </Aux>
);

ReactDOM.render(<App />, document.getElementById('root'));
dlrandy commented 6 years ago

react16之前只渲染文本的组件需要封装在span或者div里。react16移除了这种不需要的结构


const Comment = ({ text }) => {
  const emojifiedText = text
    .replace(':)', '😊')
    .replace(':D', '😀')
    .replace(':(', '🙁');
  return emojifiedText;
};

class App extends React.Component {
  render() {
    return (
      <div>
        <Comment text="Today we are sailing home :)" />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
dlrandy commented 6 years ago

React16使用Portals

默认React组件树直接映射DOM树。但是有一些情况,UI元素像overlay或者loading spinner 这种就有局限性了。React6配备了portal特性,允许在不同的根元素里追加部分组件树

border 和outline很相似不同在于: 1、虽然它们都是画在元素内容content的外面,在outline不占用空间 2、outline可以是非矩形的

ReactDOM 提供了一个createPortal方法,接收两个参数一个新的子树容器组件,第二个是目标容器

class Overlay extends React.Component {
  constructor(props) {
    super(props);
    this.overlayContainer = document.createElement('div');
    document.body.appendChild(this.overlayContainer);
  }
  componentWillUnmount() {
    document.body.removeChild(this.overlayContainer);
  }
  render() {
    return ReactDOM.createPortal(
      <div className="overlay">
        <span onClick={this.props.onClose}>X</span>
        {this.props.children}
      </div>,
      this.overlayContainer
    );
  }
}

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      overlayActive: true
    };
  }
  closeOverlay = () => {
    this.setState({
      overlayActive: false
    });
  }
  render() {
    return (
      <div>
        <h1>Dashboard</h1>
        {
          this.state.overlayActive && 
            <Overlay onClose={this.closeOverlay}>
              <div>Welcome</div>
            </Overlay>
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
.overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 260px;
  line-height: 200px;
  height: 200px;
  margin-left: -130px;
  margin-top: -100px;
  background-color: #fff;
  text-align: center;
  outline: 9999px solid rgba(0, 0, 0, 0.5);
}

.overlay div {
  display: inline;
}
<!doctype html>

<html>
  <head>
    <script src="https://unpkg.com/react@16.0.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.0.0/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
    <link rel="stylesheet" href="index.css">

  </head>

  <body>
    <div id="root"></div>
    <script type="text/jsx" src="script.jsx"></script>
  </body>
</html>
dlrandy commented 6 years ago

React16带来了一些DOM组件(div)属性的改变 1、添加自定义属性 2、可以直接将html拷贝到React的render方法里,但是class属性仍需要使用className 3、对于属性只能是字符串值或是能转成String的对象,事件句柄必须是函数

setState传递null不会触发更新。也就是可以在setState的updater方法里来决定是否更新。

dlrandy commented 6 years ago

给一个组件添加样式,可以使用className,style属性接收对象,但是这个对象没有厂商前缀,需要自己加。内置样式也会有一些问题。所以可以用一些库Popular libraries include styled components, emotion, and glamorous

react组件从props初始化state几种方式: 1、直接在constructor里初始化 2、在componentDidMount里设置state//个人不推荐使用 3、使用public class fields的时候直接读取props,其实和constructor一样

React 使用事件代理,整个document里每个类型的事件真的只有一个事件句柄,他负责管理调用你的事件句柄

react组件事件处理器的三种写法: 1、内置箭头函数或者使用了bind的函数//这个有性能影响 2、在constructor里使用bind进行绑定 3、使用public class fields:使用bind或者箭头函数 // 和默认constructor一样的情况下可以省略constructor和this

dlrandy commented 6 years ago

ref在自定义组件上引用的是实例(this),注意若是函数组件则不起作用;dom元素上是DOM; 注意一下父亲组件访问子组件dom元素的情况

react里form的onSubmit的event,里访问元素的方式: 1、event.target[n].value 2、event.target.elements.username.value 3、使用ref 注意 form === event.target

react的数组元素为什么需要key,是因为在重新渲染的时候,他需要比较新数组和旧数组,为了知道哪些选项被移除或者添加,需要使用key进行追踪。例如有一个div列表,div选项里有truthelement和一个orderedelement,如果div列表没有给div分配key,移除truthelement的时候,不一定会正确的移除orderedelement。还有就是列表里选项各自互相自动交换的时候,focus的的元素不会失去focus,,交换之后仍有focus

dlrandy commented 6 years ago

component

pureCompoennt

stateless compoennt( react15里是PureComponent , 16里就不是了,以后可能会变成Pure的, slow所以除了下面的情况,尽量不要用了 ) 适用于较小的rerender的时候cost可以忽略的;还有就是event propagation boundraies(这个我也不理解碰到再说);他还用在Component Adapter做一些检查

PureComponents 是指绝对相同的input将会产生绝对相同的output(whole nesting component tree)

作为一个PureComponent,要考虑shouldUpdateYourSelf, 还要考虑shouldUpdateAllYourChildren

pure 函数不是 PureCompoennt 在使用PureComponent的时候,不但要考虑props,state,还要考虑Context,因为PureComponent会阻塞Context的 change,但是这只是React15的问题,16的话就没有这个问题了,context的Api变了

所以一般使用PurreComonent考虑整个的Nesting Component而不是单个的组件。用作原子组件还是很安全的,但是分子组件就要小心了. 如果用于分子组件,可能要使用EventEmitter之类的

https://medium.com/@antonkorzunov/2-things-about-purecomponent-you-probable-should-know-b04844a90d4

https://hackernoon.com/restate-the-story-of-redux-tree-27d8c5d1040a https://medium.com/groww-engineering/stateless-component-vs-pure-component-d2af88a1200b

dlrandy commented 6 years ago

这段代码的错误是什么哦?

import React from "react";
import ReactDOM from "react-dom";
let TestElement = {
  $$typeof: Symbol.for("react.forward_ref"),
  render: (props, ref) => (
    <button ref={ref} className="FancyButton">
      {props.children}
    </button>)
};
const ref = React.createRef();

ReactDOM.render(<TestElement ref={ref} >hhdat</TestElement>, document.getElementById("app"));

Component Instance Element Component 是UI的声明类; Instance是Component的实例, 不同于传统的UIComponent, 他有自己的生命周期,不需要开发者手动处理子组件的创建和销毁等; Element是描述Instance或者DOM及其属性的普通对象。包括组建类型,属性和子元素。他只是告诉React你想在屏幕上看到什么 { type: '', props: { }, $$typeof: 'React_element'//处于安全的原因, render(){} } type是字符串的时候,是普通的Dom元素;当是函数或者类的时候,则是组件元素。 描述组件的元素也是元素。 Reactelement还有一个额外的$$typeof属性(处于安全的原因 XSS),这些Element有的加上$$typeof可以正常使用,有些则要使用react.createElement或者jsx重写才能正常渲染。

描述组件的元素(Element)也是元素

const DangerButton = ({ children }) => ({
 type:  Button,
 props: {
   color: 'red',
   children: children,
  }
});
const DeleteAccount = () => (
  <div>
    <p>Are you sure?</p>
    <DangerButton>Yep</DangerButton>
    <Button color='blue'>Cancel</Button>
  </div>
);

React 的元素树可以包含DOM节点的Element还可以包含描述其他组件的Elements,这样可以不依赖DOM结构组建UI。 React负责管理组件实例的

注意创建元素使用React.createElement JSX React.createFactory. 不要直接用普通的对象写Elements。只需要知道他们底层都是普通的对象。

React Elements是当React运行它的reconciliation过程的时候,将要被i渲染的对象

组件类型的React Elements会在reconciliation的时候完全的渲染成string类型的ReactElements

高阶组件工厂:componentA => ComponentB 高阶组件工厂的工厂:props => ComponentA => ComponentB

作用是 1、属性代理:就是处理传递给wrappedComponent的Props 1'处理属性 2‘通过Ref访问实例 3’state抽离 WrappedComponent不需要拥有自己的state 4‘包装WrappedComponen在其他的元素里

2、继承反转:新组件继承wrappedComponent 这个允许HOC 通过this访问WrappedComponent 实例,意味着可以访问state,props,component lifecycle hooks和render方法, 同时也能创建WrappedComponent的新的lifeCycle hooks. 继承反转的HOC不能保证整个children tree解析,Render Highjacking能够处理WrappedComponent render的Element tree 但是如果element tree里有函数组件的话,是不能够处理该组件的孩子的

1‘Render  Highjacking

Render Highjacking是因为HOC控制着WrappedComponent的render输出:

注意Render highjacking和Props Proxy的区别,他们是不能一起使用的 2’ Manipulating state

Manipulaing state: HOC可以读取和编辑,删除WrappedComponent实例的state,(为什么可以这么做?是因为当前的this就是WraapedComponet Instance),而且你也可以加入更多的state。这种方式容易弄乱WraapedComponent 实例的state, 所以一般这种情况只限于读取和增加state,对于增加state的情况最好加上namespace, 不要搞乱了wrappedComponent

ReactHOC主要是为了复用组件的逻辑

https://reactjs.org/docs/higher-order-components.html#___gatsby HOC的陷阱 1、不要在render方法里 使用HOCS reconciliation也就是react的diff算法,他检查component identity决定是否应该更新子树或者抛弃旧新建新的。使用的情况下 每次render都会新创建tree,就会重新挂载组件引起组件的state和children的丢失。 2、static的方法一定要拷贝,手动拷贝或者使用hoist-non-react-statics 3、Refs不能被传递,因为ref不是真正的prop,他引用的是外层组件的实例而不是wrappedComonent的 解决的办法是使用forwardRef, 他产生新的组件,是一个高阶组件. Ref forwarding就是让某些组件接收ref然后转发给child

https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e

dlrandy commented 6 years ago

React 的render方法创建React Element tree。再下一次的state或者props更新的时候,render会返回一个不同的React Element tree。接下来 React需要知道两个对象的不同,来高效的更新UI匹配最新的element tree。

react的启发式算法是基于两个假设: 1、不同type的元素产生不同的tree 2、开发者可以使用key提示哪些子元素是在不同的render下 是稳定的

React的Diff算法 1、比较elment的type,当然是从根元素起 比较,旧tree销毁新tree重建 2、同类型的DOM元素,比较两者的attribute,更新必要的attributes 3、同类型的Component 元素,instance不变,维护state,React会更新instance的props来匹配新的元素,最后调用render然后再diiff两个tree 4、在children上递归 5、keys

dlrandy commented 6 years ago

Why use immutable in reactjs? 因为我们需要比较数据的变化,来决定是否需要调用setState的,因为state变化是要组件树重新渲染的 ,还有就是合并数据的时候,手动的操作也会很复杂。当你的数据很复杂的时候(React的最佳实践是要state最小),这个比较是很复杂的。

immutable的意思就是当有一个对象需要改变的时候,不要对其本身进行mutate,而是创建一个新的changed 拷贝。 下面是原生的immutable方法

{...}
Object.assign
[...]
concat

但是每一次的改变都要生成一个新对象是很占用内存的,所以immutablejs使用了持久化数据结构和结构共享的技术 https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2

dlrandy commented 6 years ago

React的Async update

react将要移出的三个生命周期函数(因为不安全的coding 实践) componentWillMount/componentWillUpdate/comopnentWillReceiveProps

react16.3里引入的两个新的生命周期 static的getDerivedStateFromProps和getSnapshotBeforeUpdate getDerivedStateFromProps 是在组件实例化之后以及re-render之前。它可以返回一个对象更新state,或者null来表明新的Props不需要做任何的state更新。它和comonentDidUpdate一起使用来替换掉componentWillReceiveProps的用例。

PLACEHOLDER

getSnapshotBeforeUpdate生命周期在mutation(DOM改变之前掉用)。它的返回值将传递给comonentDidUpdate的第三个参数。它和componentDidUpudate完全可以cover compnentWillUpdate的用例。

在componentWillMount获取外部数据,在 server rendering 和即将到来的async rendering mode都会有问题。第一种请求回来的数据不会被使用;第二种的request可能被发送多次。

在componentWillMount里监听事件易造成内存泄露,因为server渲染得时候compnentWillUnmount调用不到的。async render的时候render可能会被中断,导致compnentWillUnMount不会被调用

只有compoenntDidMount被调用了才能保证componentWillUnmount被调用

getDerivedStateFromProps的 反模式: 1、无条件的copy props到state 2、当有props变化的时候,擦除state

替换的方案有: 1、Fully controlled component完全受控的组件 2、Fully uncontrolled component with a key带有key完全不受控的组件

uncontrolled components是数据由 DOM自己操纵; controlled components是数据由React Component操纵;

React的Async render 就是 render方法里获取数据暂停rendering, 这种的需要fallback,可以使用Tiemout组件作为fallback 。 'drum-roll' https://medium.com/@DidierFranc/when-react-has-become-even-more-asynchronous-37a55c3a3d3

Here's how suspending works:

https://news.ycombinator.com/item?id=16492973

https://edgecoders.com/react-16-features-and-fiber-explanation-e779544bb1b7

react16的新能力: 1、把可中断的任务划分成chunk 2、在progress中划分优先级,染合,复用任务 3、父子切换来支持layout 4、render渲染多个元素的能力 5、Portals 6、error boundary

新特性 1、核心算法重写,重写的最主要特性就是async rendering async rendering是指rendering工作可以被划分成chunks,分布到多个frames

dlrandy commented 6 years ago

手机上的一个click会触发哪些事件? touchstart touchmove touchend mouseover mousemove mousedown mouseup click 奇怪了手机上我用jq监听的是mouseenter啊?为啥事件触发了? 因为jquery使用的mouseover + event-time check代理的mouseenter。

https://www.html5rocks.com/en/mobile/touchandmouse/ https://github.com/jquery/jquery/blob/master/src/event.js

mouseenter 和mouseover 有啥区别阿? 前者不冒泡;

mouseleave和mouseout的却别? 前者不冒泡。

https://w3c.github.io/touch-events/#mouse-events