React 16 新特性全解(上)

前言

本次系列分上下兩篇文章,上主要介紹從v16.0~ 16.4的新特性,下主要介紹16.5~16.8。下面就開始吧~javascript

本篇文章較長預計須要15min(固然主要是由於demo太多),你們能夠搞點瓜子邊啃邊看。最好能留出一隻手本身在codePen上本身調試一下。html

歡迎關注個人知乎專欄,獲取更多文章信息前端

目錄

v16.0java

  1. render 支持返回數組和字符串 演示
  2. Error Boundary
  3. createPortal
  4. 支持自定義 DOM 屬性
  5. Fiber
  6. 提高SSR渲染速度
  7. 減少文件體積

v16.1node

react-call-returnreact

v16.2git

Fragmentgithub

v16.3算法

  1. 生命週期函數的更新
  2. createContext
  3. createRef
  4. forwardRef
  5. strict Mode

下面就開始吧~api

v16.0

主要特性:

1、render能夠返回字符串,數組,數字

React 15: 只能夠返回單一組件,也就是說即便你返回的是一個string,也須要用div包住。

function MyComponent() {
  return (
    <div>
       hello world
    <div>
  );
}
複製代碼

React 16: 支持返回這五類:React elements, 數組和Fragments,Portal,String/numbers,boolean/null。

class Example extends React.Component {
  render() {
    return [
      <div key="1">first element</div>,
      <div key="2">second element</div>,
    ];
  }
}
複製代碼

注意:不管返回的形式是怎麼樣的,都要保持render是一個純函數。因此要求咱們不要改state的狀態,同時不要直接跟瀏覽器直接交互,讓它每次調用生成的結果都是一致的。

2、Error boundary(錯誤邊界)

React 15:渲染過程當中有出錯,直接crash整個頁面,而且錯誤信息不明確,可讀性差

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  componentWillMount() {
    throw new Error('I am crash');
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
      <hr />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製代碼

demo地址

好比上面這個App,能夠看到子組件BuggyCounter出了點問題,在沒有Error Boundary的時候,整個App都會crash掉,因此顯示白屏。

React 16:用於捕獲子組件樹的JS異常(即錯誤邊界只能夠捕獲組件在樹中比他低的組件錯誤。),記錄錯誤並展現一個回退的UI。

捕獲範圍:

  1. 渲染期間
  2. 生命週期內
  3. 整個組件樹構造函數內

如何使用:

// 先定一個組件ErrorBoundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    // 有錯誤的時候展現回退
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // 正常的話,直接展現組件
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  componentWillMount() {
    throw new Error('I am crash');
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
        <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        </ErrorBoundary>
      <hr />

    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製代碼

demo演示:

能夠看到加上Error Boundary以後,除了出錯的組件,其餘的地方都不受影響。

並且它很清晰的告訴咱們是哪一個組件發生了錯誤。

注意事項:

Error Boundary沒法捕獲下面的錯誤:

一、事件函數裏的錯誤

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}
複製代碼

上面的例子中,handleClick方法裏面發生的錯誤,Error Boundary是捕獲不到的。由於它不發生在渲染階段,因此採用try/catch來捕獲。

二、異步代碼(例如setTimeout 或 requestAnimationFrame 回調函數)

class A extends React.Component {
     render() {
        // 此錯誤沒法被捕獲,渲染時組件正常返回 `<div></div>`
        setTimeout(() => {
            throw new Error('error')
        }, 1000)
        return (
            <div></div>
        )
    }
}
複製代碼

三、服務端渲染

由於服務器渲染不支持Error Boundary

四、Error Boundary自身拋出來的錯誤 (而不是其子組件)

那這裏還遺留一個問題?錯誤邊界放在哪裏。通常來講,有兩個地方:

一、能夠放在頂層,告訴用戶有東西出錯。可是我我的不建議這樣,這感受失去了錯誤邊界的意義。由於有一個組件出錯了,其餘正常的也沒辦法正常顯示了

二、包在子組件外面,保護其餘應用不崩潰。

3、react portal

在介紹這個新特性以前,咱們先來看看爲何須要portal。在沒有portal以前,若是咱們須要寫一個Dialog組件,咱們會這樣寫。

<div class="app">
   <div> ... </div>
   { needDialog ? <Dialog /> : null }
</div>
複製代碼

問題:

一、最終渲染產生的html存在於JSX產生的HTML在一塊兒,這時候dialog 若是須要position:absolute 控制位置的話,須要保證dialog 往上沒有position:relative 的干擾。

二、層級關係不清晰,dialog實際是獨立在app以外的。

因此這時候Portal降臨。

Portal能夠幫助咱們在JSX中跟普通組件同樣直接使用dialog, 可是又可讓dialog內容層級不在父組件內,而是顯示在獨立於原來app在外的同層級組件。

如何使用:

HTML:

<div id="app-root"></div>
// 這裏爲咱們定義Dialog想要放入的位置
<div id="modal-root"></div>
複製代碼

JS:

// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

// Let's create a Modal component that is an abstraction around // the portal API. class Modal extends React.Component { constructor(props) { super(props); // Create a div that we'll render the modal into. Because each
    // Modal component has its own element, we can render multiple
    // modal components into the modal container.
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // Append the element into the DOM on mount. We'll render // into the modal container element (see the HTML tab). // 這邊會將咱們生成的portal element插入到modal-root裏。 modalRoot.appendChild(this.el); } componentWillUnmount() { // Remove the element from the DOM when we unmount modalRoot.removeChild(this.el); } render() { // Use a portal to render the children into the element return ReactDOM.createPortal( // Any valid React child: JSX, strings, arrays, etc. this.props.children, // A DOM element this.el, ); } } // The Modal component is a normal React component, so we can // render it wherever we like without needing to know that it's
// implemented with portals.
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showModal: false};
    
    this.handleShow = this.handleShow.bind(this);
    this.handleHide = this.handleHide.bind(this);
  }

  handleShow() {
    this.setState({showModal: true});
  }
  
  handleHide() {
    this.setState({showModal: false});
  }

  render() {
    // Show a Modal on click.
    // (In a real app, don't forget to use ARIA attributes // for accessibility!) const modal = this.state.showModal ? ( <div className="modal"> <div> With a portal, we can render content into a different part of the DOM, as if it were any other React child. </div> This is being rendered inside the #modal-container div. <button onClick={this.handleHide}>Hide modal</button> </div> ) : null; return ( <div className="app"> This div has overflow: hidden. <button onClick={this.handleShow}>Show modal</button> {modal} </div> ); } } ReactDOM.render(<App />, appRoot); 複製代碼
Example: Portals​
codepen.io圖標

沒有portal生成與有portal的時候生成的層級關係以下:

Example: Portals​
codepen.io圖標

能夠很清楚的看到,使用portal以後,modal不在嵌在app-root裏。

4、自定義DOM屬性

React 15:忽略未標準化的html 和 svg屬性

React 16:去掉了這個限制

爲何要作這個改動呢?兩個緣由:

  1. 不能用自定義屬性,對於非標準(proposal階段)新屬性還有其餘框架(Angular)很不友好
  2. React 15之因此能夠過濾掉非標準的屬性,是由於他們維護了一個白名單的文件(放在bundle size 裏)。而隨着時間的增長,標準化的屬性愈來愈多,意味着要一直維護這個文件,同時這個文件也會愈來愈大,增長bundle的體積。

因此還不如去掉這個限制。

演示

能夠看到自定義屬性已經生效了。

5、優化SSR

具體優化了下面五個方面:

  1. 生成更簡潔的HTML
  2. 寬鬆的客戶端一致性校驗
  3. 無需提早編譯
  4. react 16服務端渲染速度更快
  5. 支持流式渲染

一、生成更簡潔的HTML

先看下面的HTML,react 15與react 16的服務端分別會生成什麼。

renderToString(  
 <div>     
   This is some <span>server-generated</span> <span>HTML.</span> 
 </div> );
複製代碼

react15:

有data-reactid, text noded ,react-text各類屬性。

<div data-reactroot="" data-reactid="1" 
    data-react-checksum="122239856">
  <!-- react-text: 2 -->This is some <!-- /react-text -->
  <span data-reactid="3">server-generated</span>
  <!-- react-text: 4--> <!-- /react-text -->
  <span data-reactid="5">HTML.</span>
</div>
複製代碼

react 16:

<div data-reactroot="">   
    This is some <span>server-generated</span> 
    <span>HTML.</span> 
</div>
複製代碼

能夠看到,react 16去掉了不少屬性,它的好處很明顯:增長易讀性,同時很大程度上減小html的文件大小。

二、寬鬆的客戶端一致性校驗

react 15:會將SSR的結果與客戶端生成的作一個個字節的對比校驗 ,一點不匹配發出waring同時就替換整個SSR生成的樹。

react 16:對比校驗會更寬鬆一些,好比,react 16容許屬性順序不一致,並且遇到不匹配的標籤,還會作子樹的修改,不是整個替換。

注意點: react16不會自動fix SSR 屬性跟client html屬性的不一樣,可是仍然會報waring,因此咱們須要本身手動去修改。

三、無需提早編譯

react 15:若是你直接使用SSR,會有不少須要檢查procee.env的地方,可是讀取在node中讀取process.env是很消耗時間的。因此在react 15的時候,須要提早編譯,這樣就能夠移除 process.env的引用。

react 16:只有一次檢查process.env的地方,因此就不須要提早編譯了,能夠開箱即用。

四、react 16服務端渲染速度更快

爲何呢? 由於react 15下,server client都須要生成vDOM,可是其實在服務端, 當咱們使用renderToString的時候,生成的vDom就會被當即拋棄掉, 因此在server端生成vDom是沒有意義的。

圖片來源(https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67)

五、支持流式渲染

使用流式渲染會提高首個字節到(TTFB)的速度。可是什麼是流式渲染呢?

能夠理解爲內容以一種流的形式傳給前端。因此在下一部分的內容被生成以前,開頭的內容就已經被髮到瀏覽器端了。這樣瀏覽器就能夠更早的編譯渲染文件內容。

// using Express
import { renderToNodeStream } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
  res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
  res.write("<div id='content'>");
  const stream = renderToNodeStream(<MyPage/>);
  stream.pipe(res, { end: false });
  stream.on('end', () => {
    res.write("</div></body></html>");
    res.end();
  });
});
複製代碼


新的API server: renderyToNodeStream, renderToStaticNodeStream (renderToString, renderToStaticMarkup) client: hydyate

如何使用:

React 15:

// server:
// using Express client 
import { renderToString } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
  res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
  res.write("<div id='content'>");  
  res.write(renderToString(<MyPage/>));
  res.write("</div></body></html>");
  res.end();
});

// client
import { render } from "react-dom"
import MyPage from "./MyPage"
render(<MyPage/>, document.getElementById("content"));
複製代碼

React 16:

其實就是吧client端的render改爲hydrate。

// client
import { hydrate } from "react-dom"
import MyPage from "./MyPage"
hydrate(<MyPage/>, document.getElementById("content"));
複製代碼

固然,如今依然兼容render,可是17以後再也不兼容,因此仍是直接用hydrate好一點。

注意事項:不支持ErrorBoundary 跟Portal,因此須要直出的頁面就不能用了。

5、減少了32%bundle的體積

React 庫大小從 20.7kb(壓縮後 6.9kb)下降到 5.3kb(壓縮後 2.2kb)

ReactDOM 庫大小從 141kb(壓縮後 42.9kb)下降到 103.7kb(壓縮後 32.6kb)

React + ReactDOM 庫大小從 161.7kb(壓縮後 49.8kb)下降到 109kb(壓縮後 43.8kb)

6、Fiber

因爲Fiber不是新的API,是react對於對比更新的一種新算法,它影響着生命週期函數的變化跟異步渲染。須要詳細瞭解的同窗能夠戳下面的連接,這應該是我看過最易懂得解釋Fiber得視頻。

https://www.youtube.com/watch?v=VLAqywvHpD0​
www.youtube.com

v16.1

react-call-return

這就是一個庫,平時用的比較少,因此暫時不講。

v16.2

主要特性:Fragement

React 15:render函數只能接受一個組件,因此必定要外層包一層<div>。

React16:能夠經過Fragement直接返回多個組件。

render() {
 return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}
複製代碼

可是這樣看起來,彷佛能夠用v16.0 return一個數組搞定。

可是返回數組是有缺點的,好比:這段html

Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
複製代碼

用Fragment寫,方便又快捷:

render() {
  return (
 // Extraneous div element :(
    <Fragement>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </Fragement>
  );
}
複製代碼

用數組寫.... 一言難盡(什麼,你還沒看出來有什麼區別!下面我來帶你)

render() {
 return [
  "Some text.",
  <h2 key="heading-1">A heading</h2>,
  "More text.",
  <h2 key="heading-2">Another heading</h2>,
  "Even more text."
 ];
}
複製代碼

缺點:

  • 數組裏的子節點必需要用逗號分離
  • 數組裏的子節點必需要帶key防止waring
  • string類型要用雙引號括住

因此,Fragement仍是很大程度上給咱們提供了便利。

注意點:

<> </> 不支持寫入屬性,包括keys。若是你須要keys,你能夠直接使用<Fragment> (可是Fragment也只能夠接受keys這一個屬性,未來會支持更多)

function Glossary(props) {
 return (
    <dl>
      {props.items.map(item => (
        // Without the `key`, React will fire a key warning
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}
複製代碼

官方演示

好了,相信看到這裏,你們都很想睡覺了。堅持一下,還有五個點就講完了~。


前言

本次系列分上下兩篇文章,上主要介紹從v16.0~ 16.4的新特性,下主要介紹16.5~16.8。下面就開始吧~

本篇文章較長預計須要15min(固然主要是由於demo太多),你們能夠搞點瓜子邊啃邊看。最好能留出一隻手本身在codePen上本身調試一下。

目錄

v16.0

  1. render 支持返回數組和字符串 演示
  2. Error Boundary
  3. createPortal
  4. 支持自定義 DOM 屬性
  5. Fiber
  6. 提高SSR渲染速度
  7. 減少文件體積

v16.1

react-call-return

v16.2

Fragment

v16.3

  1. 生命週期函數的更新
  2. createContext
  3. createRef
  4. forwardRef
  5. strict Mode

下面就開始吧~

v16.0

主要特性:

1、render能夠返回字符串,數組,數字

React 15: 只能夠返回單一組件,也就是說即便你返回的是一個string,也須要用div包住。

function MyComponent() {
  return (
    <div>
       hello world
    <div>
  );
}
複製代碼

React 16: 支持返回這五類:React elements, 數組和Fragments,Portal,String/numbers,boolean/null。

class Example extends React.Component {
  render() {
    return [
      <div key="1">first element</div>,
      <div key="2">second element</div>,
    ];
  }
}
複製代碼

注意:不管返回的形式是怎麼樣的,都要保持render是一個純函數。因此要求咱們不要改state的狀態,同時不要直接跟瀏覽器直接交互,讓它每次調用生成的結果都是一致的。

2、Error boundary(錯誤邊界)

React 15:渲染過程當中有出錯,直接crash整個頁面,而且錯誤信息不明確,可讀性差

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  componentWillMount() {
    throw new Error('I am crash');
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
      <hr />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製代碼

demo地址

好比上面這個App,能夠看到子組件BuggyCounter出了點問題,在沒有Error Boundary的時候,整個App都會crash掉,因此顯示白屏。

React 16:用於捕獲子組件樹的JS異常(即錯誤邊界只能夠捕獲組件在樹中比他低的組件錯誤。),記錄錯誤並展現一個回退的UI。

捕獲範圍:

  1. 渲染期間
  2. 生命週期內
  3. 整個組件樹構造函數內

如何使用:

// 先定一個組件ErrorBoundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    // 有錯誤的時候展現回退
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // 正常的話,直接展現組件
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  componentWillMount() {
    throw new Error('I am crash');
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
        <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        </ErrorBoundary>
      <hr />

    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
複製代碼

demo演示:

能夠看到加上Error Boundary以後,除了出錯的組件,其餘的地方都不受影響。

並且它很清晰的告訴咱們是哪一個組件發生了錯誤。

注意事項:

Error Boundary沒法捕獲下面的錯誤:

一、事件函數裏的錯誤

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}
複製代碼

上面的例子中,handleClick方法裏面發生的錯誤,Error Boundary是捕獲不到的。由於它不發生在渲染階段,因此採用try/catch來捕獲。

二、異步代碼(例如setTimeout 或 requestAnimationFrame 回調函數)

class A extends React.Component {
     render() {
        // 此錯誤沒法被捕獲,渲染時組件正常返回 `<div></div>`
        setTimeout(() => {
            throw new Error('error')
        }, 1000)
        return (
            <div></div>
        )
    }
}
複製代碼

三、服務端渲染

由於服務器渲染不支持Error Boundary

四、Error Boundary自身拋出來的錯誤 (而不是其子組件)

那這裏還遺留一個問題?錯誤邊界放在哪裏。通常來講,有兩個地方:

一、能夠放在頂層,告訴用戶有東西出錯。可是我我的不建議這樣,這感受失去了錯誤邊界的意義。由於有一個組件出錯了,其餘正常的也沒辦法正常顯示了

二、包在子組件外面,保護其餘應用不崩潰。

3、react portal

在介紹這個新特性以前,咱們先來看看爲何須要portal。在沒有portal以前,若是咱們須要寫一個Dialog組件,咱們會這樣寫。

<div class="app">
   <div> ... </div>
   { needDialog ? <Dialog /> : null }
</div>
複製代碼

問題:

一、最終渲染產生的html存在於JSX產生的HTML在一塊兒,這時候dialog 若是須要position:absolute 控制位置的話,須要保證dialog 往上沒有position:relative 的干擾。

二、層級關係不清晰,dialog實際是獨立在app以外的。

因此這時候Portal降臨。

Portal能夠幫助咱們在JSX中跟普通組件同樣直接使用dialog, 可是又可讓dialog內容層級不在父組件內,而是顯示在獨立於原來app在外的同層級組件。

如何使用:

HTML:

<div id="app-root"></div>
// 這裏爲咱們定義Dialog想要放入的位置
<div id="modal-root"></div>
複製代碼

JS:

// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

// Let's create a Modal component that is an abstraction around // the portal API. class Modal extends React.Component { constructor(props) { super(props); // Create a div that we'll render the modal into. Because each
    // Modal component has its own element, we can render multiple
    // modal components into the modal container.
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // Append the element into the DOM on mount. We'll render // into the modal container element (see the HTML tab). // 這邊會將咱們生成的portal element插入到modal-root裏。 modalRoot.appendChild(this.el); } componentWillUnmount() { // Remove the element from the DOM when we unmount modalRoot.removeChild(this.el); } render() { // Use a portal to render the children into the element return ReactDOM.createPortal( // Any valid React child: JSX, strings, arrays, etc. this.props.children, // A DOM element this.el, ); } } // The Modal component is a normal React component, so we can // render it wherever we like without needing to know that it's
// implemented with portals.
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showModal: false};
    
    this.handleShow = this.handleShow.bind(this);
    this.handleHide = this.handleHide.bind(this);
  }

  handleShow() {
    this.setState({showModal: true});
  }
  
  handleHide() {
    this.setState({showModal: false});
  }

  render() {
    // Show a Modal on click.
    // (In a real app, don't forget to use ARIA attributes // for accessibility!) const modal = this.state.showModal ? ( //注意~~~~~~~~~~~~~這裏能夠自行加上這個調試 // <Modal> <div className="modal"> <div> With a portal, we can render content into a different part of the DOM, as if it were any other React child. </div> This is being rendered inside the #modal-container div. <button onClick={this.handleHide}>Hide modal</button> </div> //</Modal> ) : null; return ( <div className="app"> This div has overflow: hidden. <button onClick={this.handleShow}>Show modal</button> {modal} </div> ); } } ReactDOM.render(<App />, appRoot); 複製代碼

Example: Portals​

codepen.io

沒有portal生成與有portal的時候生成的層級關係以下:

能夠很清楚的看到,使用portal以後,modal不在嵌在app-root裏。

4、自定義DOM屬性

React 15:忽略未標準化的html 和 svg屬性

React 16:去掉了這個限制

爲何要作這個改動呢?兩個緣由:

  1. 不能用自定義屬性,對於非標準(proposal階段)新屬性還有其餘框架(Angular)很不友好
  2. React 15之因此能夠過濾掉非標準的屬性,是由於他們維護了一個白名單的文件(放在bundle size 裏)。而隨着時間的增長,標準化的屬性愈來愈多,意味着要一直維護這個文件,同時這個文件也會愈來愈大,增長bundle的體積。

因此還不如去掉這個限制。

演示

能夠看到自定義屬性已經生效了。

5、優化SSR

具體優化了下面五個方面:

  1. 生成更簡潔的HTML
  2. 寬鬆的客戶端一致性校驗
  3. 無需提早編譯
  4. react 16服務端渲染速度更快
  5. 支持流式渲染

一、生成更簡潔的HTML

先看下面的HTML,react 15與react 16的服務端分別會生成什麼。

renderToString(  
 <div>     
   This is some <span>server-generated</span> <span>HTML.</span> 
 </div> );
複製代碼

react15:

有data-reactid, text noded ,react-text各類屬性。

<div data-reactroot="" data-reactid="1" 
    data-react-checksum="122239856">
  <!-- react-text: 2 -->This is some <!-- /react-text -->
  <span data-reactid="3">server-generated</span>
  <!-- react-text: 4--> <!-- /react-text -->
  <span data-reactid="5">HTML.</span>
</div>
複製代碼

react 16:

<div data-reactroot="">   
    This is some <span>server-generated</span> 
    <span>HTML.</span> 
</div>
複製代碼

能夠看到,react 16去掉了不少屬性,它的好處很明顯:增長易讀性,同時很大程度上減小html的文件大小。

二、寬鬆的客戶端一致性校驗

react 15:會將SSR的結果與客戶端生成的作一個個字節的對比校驗 ,一點不匹配發出waring同時就替換整個SSR生成的樹。

react 16:對比校驗會更寬鬆一些,好比,react 16容許屬性順序不一致,並且遇到不匹配的標籤,還會作子樹的修改,不是整個替換。

注意點: react16不會自動fix SSR 屬性跟client html屬性的不一樣,可是仍然會報waring,因此咱們須要本身手動去修改。

三、無需提早編譯

react 15:若是你直接使用SSR,會有不少須要檢查procee.env的地方,可是讀取在node中讀取process.env是很消耗時間的。因此在react 15的時候,須要提早編譯,這樣就能夠移除 process.env的引用。

react 16:只有一次檢查process.env的地方,因此就不須要提早編譯了,能夠開箱即用。

四、react 16服務端渲染速度更快

爲何呢? 由於react 15下,server client都須要生成vDOM,可是其實在服務端, 當咱們使用renderToString的時候,生成的vDom就會被當即拋棄掉, 因此在server端生成vDom是沒有意義的。

圖片來源(https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67)

五、支持流式渲染

會提高首個字節到的速度,不過我試了一下,會閃屏,因此我不太推薦使用/除非咱們的頁面作到一個空的框架先來,內容在填充。

新的API server: renderyToNodeStream, renderToStaticNodeStream (renderToString, renderToStaticMarkup) client: hydyate

如何使用:

React 15:

// server:
// using Express client 
import { renderToString } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
  res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
  res.write("<div id='content'>");  
  res.write(renderToString(<MyPage/>));
  res.write("</div></body></html>");
  res.end();
});

// client
import { render } from "react-dom"
import MyPage from "./MyPage"
render(<MyPage/>, document.getElementById("content"));
複製代碼

React 16:

其實就是吧client端的render改爲hydrate。

// client
import { hydrate } from "react-dom"
import MyPage from "./MyPage"
hydrate(<MyPage/>, document.getElementById("content"));
複製代碼

固然,如今依然兼容render,可是17以後再也不兼容,因此仍是直接用hydrate好一點。

注意事項:不支持ErrorBoundary 跟Portal,因此須要直出的頁面就不能用了。

5、減少了32%bundle的體積

React 庫大小從 20.7kb(壓縮後 6.9kb)下降到 5.3kb(壓縮後 2.2kb)

ReactDOM 庫大小從 141kb(壓縮後 42.9kb)下降到 103.7kb(壓縮後 32.6kb)

React + ReactDOM 庫大小從 161.7kb(壓縮後 49.8kb)下降到 109kb(壓縮後 43.8kb)

6、Fiber

因爲Fiber不是新的API,是react對於對比更新的一種新算法,它影響着生命週期函數的變化跟異步渲染。須要詳細瞭解的同窗能夠戳下面的連接,這應該是我看過最易懂得解釋Fiber得視頻。

www.youtube.com/watch?v=VLA…

www.youtube.com

v16.1

react-call-return

這就是一個庫,平時用的比較少,因此暫時不講。

v16.2

主要特性:Fragement

React 15:render函數只能接受一個組件,因此必定要外層包一層<div>。

React16:能夠經過Fragement直接返回多個組件。

render() {
 return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}
複製代碼

可是這樣看起來,彷佛能夠用v16.0 return一個數組搞定。

可是返回數組是有缺點的,好比:這段html

Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
複製代碼

用Fragment寫,方便又快捷:

render() {
  return (
 // Extraneous div element :(
    <Fragement>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </Fragement>
  );
}
複製代碼

用數組寫.... 一言難盡(什麼,你還沒看出來有什麼區別!下面我來帶你)

render() {
 return [
  "Some text.",
  <h2 key="heading-1">A heading</h2>,
  "More text.",
  <h2 key="heading-2">Another heading</h2>,
  "Even more text."
 ];
}
複製代碼

缺點:

  • 數組裏的子節點必需要用逗號分離
  • 數組裏的子節點必需要帶key防止waring
  • string類型要用雙引號括住

因此,Fragement仍是很大程度上給咱們提供了便利。

注意點:

<> </> 不支持寫入屬性,包括keys。若是你須要keys,你能夠直接使用<Fragment> (可是Fragment也只能夠接受keys這一個屬性,未來會支持更多)

function Glossary(props) {
 return (
    <dl>
      {props.items.map(item => (
        // Without the `key`, React will fire a key warning
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}
複製代碼

官方演示

好了,相信看到這裏,你們都很想睡覺了。堅持一下,還有五個點就講完了~。

16.3

1、新的生命週期函數

因爲異步渲染的改動,有可能會致使componentWillMount, componentWillReceiveProps,componentWillUpdate ,因此須要拋棄三個函數。

因爲這是一個很大的改變會影響不少現有的組件,因此須要慢慢的去改。 目前react 16 只是會報waring,在react 17你就只能在前面加"UNSAFE_" 的前綴 來使用。不能不說react團隊真是太貼心了,他們還寫了一個腳本自動幫你加上 這些前綴。瘋狂打call~

同時新加了兩個生命週期函數來替代他們,分別是:

getDerivedStateFromProps:這個方法用於替代componentWillReceiveProps,相關內容能夠看這篇文章,可是大多數狀況下,都不須要用到這兩種方法。 由於你均可以用其餘辦法來替代。

而getSnapshotBeforeUpate使用的場景不多,這裏就不介紹了。

2、新的context API

一、context 就是可使用全局的變量,不須要一層層pass props下去,好比主題顏色

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
 // Use a Provider to pass the current theme to the tree below.
 // Any component can read it, no matter how deep it is.
 // In this example, we're passing "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
 return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
 // Assign a contextType to read the current theme context.
 // React will find the closest theme Provider above and use its value.
 // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
 return <Button theme={this.context} />;
  }
}
複製代碼

可是須要謹慎使用,由於這會讓你的組件複用性變差。 通常來講,若是你只是想避免須要傳不少次props的話,能夠直接使用component composition(就是經過props本身傳給指定的)會更好。 例如:

function Page(props) {
 const user = props.user;
 // 簡單來講就是直接父組件將props傳下去
 const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
 return <PageLayout userLink={userLink} />;
}

// Now, we have:
<Page user={user} />
// ... which renders ...
<PageLayout userLink={...} />
// ... which renders ...
<NavigationBar userLink={...} />
// ... which renders ...
{props.userLink}
複製代碼

什麼場景下須要用context? 一些相同的data須要被大多的component用到,而且仍是在不一樣的層級裏。 通常用於主題,存儲數據等。

3、createRef API

react15 的時候提供了兩種refs的方法: string 跟 callback string:

class MyComponent extends React.Component {
  constructor(props) {
 super(props);

 
  }
 // 經過this.refs.textInput 來獲取

  render() {
 return <input type="text" ref='textInput' />;
  }
}
callback:
class MyComponent extends React.Component {
  constructor(props) {
 super(props);

 
  }
 // 經過this.textInput 來獲取

  render() {
 return <input type="text" ref={element => this.textInput = element} />;
  }
}
複製代碼

因爲用string的方式會致使一些潛在的問題,因此以前推薦使用callback。可是用string的方法明顯方便一點啊喂~

因此react 團隊get到了你們的需求,又出了一個新的api 能夠用string的方式並且尚未缺點, 真是可喜可賀,可口可樂。

class MyComponent extends React.Component {
  constructor(props) {
 super(props);

 this.inputRef = React.createRef();
  }

  render() {
 return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
 this.inputRef.current.focus();
  }
}
複製代碼

使用場景:

  1. 用於操做focus, text 選擇,media playback
  2. 觸發即時動畫
  3. 與第三方組件結合

注意事項:

一、functional component 是不能傳ref屬性的,由於他們沒有instance

function MyFunctionComponent() {
 return <input />;
}

class Parent extends React.Component {
  constructor(props) {
 super(props);
 this.textInput = React.createRef();
  }
  render() {
 // 這個不能工做
 return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}
複製代碼

可是!只要你要引用的對象是DOM元素或者是class component, 那你能夠在functional component裏可使用ref屬性

function CustomTextInput(props) {
 // textInput must be declared here so the ref can refer to it
  let textInput = React.createRef();

 function handleClick() {
    textInput.current.focus();
  }

 return (
    <div>
      <input
        type="text"
        ref={textInput} />

      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}
複製代碼

簡而言之:functional component裏可使用refs 可是不能把ref屬性給它自己。

4、forwardRef API

使用場景: 父組件須要將本身的引用傳給子組件

const TextInput = React.forwardRef((props, ref) => (
  <input type="text" placeholder="Hello forwardRef" ref={ref} />
))

const inputRef = React.createRef()

class App extends Component {
  constructor(props) {
 super(props)
 
 this.myRef = React.createRef()
  }

  handleSubmit = event => {
    event.preventDefault()
 
    alert('input value is:' + inputRef.current.value)
  }
 
  render() {
 return (
      <form onSubmit={this.handleSubmit}>
        <TextInput ref={inputRef} />
        <button type="submit">Submit</button>
      </form>
    )
  }
}
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
複製代碼

這樣咱們就能夠直接用this.ref 拿到對button的引用。 若是你寫的是一個高階組件,那麼推薦使用forwardAPI 將ref傳給下面的component。

5、strictMode component

嚴格模式用來幫助開發者發現潛在問題的工具。就像Fragment 同樣,它不會render任何的DOM 元素。注意:只有在development模式下才能用。

它能夠幫助咱們:

  1. 識別出使用不安全生命週期的組件
  2. 對使用string ref進行告警
  3. 對使用findDOMNode進行告警
  4. 探測某些產生反作用的方法
  5. 對使用棄用context API進行警告

還會有更多的功能在後續版本加進來。

使用:

function ExampleApplication() {
 return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}
複製代碼



參考文檔:

一、React Portal

二、官方文檔

三、www.ayqy.net/blog/react-…

四、React 16 SSR 新特性

五、Refs and the Dom

相關文章
相關標籤/搜索