本次系列分上下两篇文章,上主要介绍从v16.0~ 16.4的新特性,下主要介绍16.5~16.8。下面就开始吧~javascript
本篇文章较长预计须要15min(固然主要是由于demo太多),你们能够搞点瓜子边啃边看。最好能留出一只手本身在codePen上本身调试一下。html
欢迎关注个人知乎专栏,获取更多文章信息前端
v16.0java
v16.1node
react-call-returnreact
v16.2git
Fragmentgithub
v16.3算法
下面就开始吧~api
主要特性:
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')
);
复制代码
好比上面这个App,能够看到子组件BuggyCounter出了点问题,在没有Error Boundary的时候,整个App都会crash掉,因此显示白屏。
React 16:用于捕获子组件树的JS异常(即错误边界只能够捕获组件在树中比他低的组件错误。),记录错误并展现一个回退的UI。
捕获范围:
如何使用:
// 先定一个组件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')
);
复制代码
能够看到加上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自身抛出来的错误 (而不是其子组件)
那这里还遗留一个问题?错误边界放在哪里。通常来讲,有两个地方:
一、能够放在顶层,告诉用户有东西出错。可是我我的不建议这样,这感受失去了错误边界的意义。由于有一个组件出错了,其余正常的也没办法正常显示了
二、包在子组件外面,保护其余应用不崩溃。
在介绍这个新特性以前,咱们先来看看为何须要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); 复制代码
没有portal生成与有portal的时候生成的层级关系以下:
能够很清楚的看到,使用portal以后,modal不在嵌在app-root里。
4、自定义DOM属性
React 15:忽略未标准化的html 和 svg属性
React 16:去掉了这个限制
为何要作这个改动呢?两个缘由:
因此还不如去掉这个限制。
能够看到自定义属性已经生效了。
5、优化SSR
具体优化了下面五个方面:
一、生成更简洁的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是没有意义的。
五、支持流式渲染
使用流式渲染会提高首个字节到(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=VLAqywvHpD0react-call-return
这就是一个库,平时用的比较少,因此暂时不讲。
主要特性: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."
];
}
复制代码
缺点:
因此,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
v16.1
react-call-return
v16.2
Fragment
v16.3
下面就开始吧~
主要特性:
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')
);
复制代码
好比上面这个App,能够看到子组件BuggyCounter出了点问题,在没有Error Boundary的时候,整个App都会crash掉,因此显示白屏。
React 16:用于捕获子组件树的JS异常(即错误边界只能够捕获组件在树中比他低的组件错误。),记录错误并展现一个回退的UI。
捕获范围:
如何使用:
// 先定一个组件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')
);
复制代码
能够看到加上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自身抛出来的错误 (而不是其子组件)
那这里还遗留一个问题?错误边界放在哪里。通常来讲,有两个地方:
一、能够放在顶层,告诉用户有东西出错。可是我我的不建议这样,这感受失去了错误边界的意义。由于有一个组件出错了,其余正常的也没办法正常显示了
二、包在子组件外面,保护其余应用不崩溃。
在介绍这个新特性以前,咱们先来看看为何须要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); 复制代码
没有portal生成与有portal的时候生成的层级关系以下:
能够很清楚的看到,使用portal以后,modal不在嵌在app-root里。
4、自定义DOM属性
React 15:忽略未标准化的html 和 svg属性
React 16:去掉了这个限制
为何要作这个改动呢?两个缘由:
因此还不如去掉这个限制。
能够看到自定义属性已经生效了。
5、优化SSR
具体优化了下面五个方面:
一、生成更简洁的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是没有意义的。
五、支持流式渲染
会提高首个字节到的速度,不过我试了一下,会闪屏,因此我不太推荐使用/除非咱们的页面作到一个空的框架先来,内容在填充。
新的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得视频。
react-call-return
这就是一个库,平时用的比较少,因此暂时不讲。
主要特性: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."
];
}
复制代码
缺点:
因此,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>
);
}
复制代码
好了,相信看到这里,你们都很想睡觉了。坚持一下,还有五个点就讲完了~。
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();
}
}
复制代码
使用场景:
注意事项:
一、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模式下才能用。
它能够帮助咱们:
还会有更多的功能在后续版本加进来。
使用:
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
复制代码
参考文档:
二、官方文档