react 16出來這么久了,你工作中用了多少?。?!
REACT16 新特性總結
起因:(由于之前使用react導致好多用法還是保持原來的習慣,希望通過此次總結能將react16的新特性引用到實際工作中。)
生命周期
廢棄的周期:componentWillMount、componentWillReceiveProps、componentWillUpdate
新增周期:getDerivedStateFromProps、getSnapshotBeforeUpdate
react15的生命

React16 的生命周期:

生命周期主要的變化 getDerivedStateFromProps 替代了componentWillMount、componentWillReceiveProps,并且setState的時候 也會執(zhí)行。 getDerivedStateFromProps 的返回值是新的state對象。 用他的時候要小心,不要有副作用,最好是純函數(shù)。因為現(xiàn)在是無論是state改變還是props改變,都會執(zhí)行。
static getDerivedStateFromProps(props, state) {
if ( props.name !== state.name ) {
return {
name: props.name,
list: null
};
}
return null;
}
render 函數(shù)的返回值(數(shù)組或字符串)
class LiArray extends Component {
render () {
return [
<li>可以返回數(shù)組</li>,
<li>可以返回字符串</li>,
<li>可以返回字符串</li>
]
}
}
// 與上面類似的也可以用組件替換 或者 用空標簽 <></>
class LiArray extends Component {
render () {
return (
<React.Fragment>// <></>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
)
}
}
class LiArray extends Component {
render () {
return 'my name is zhenganlin'
}
}
Error Boundary 組件(用來捕獲子組件的render錯誤)
// 新增 componentDidCatch 鉤子函數(shù)
class ErrorBoundary extends Component {
constructor () {
super()
this.state = {
hasError: false
}
}
componentDidCatch = (err, info) => {
this.setState({
hasError: true
})
console.log('內部組件報錯了', err, info)
}
render () {
if (this.state.hasError) {
return <h1>內部組件報錯了</h1>
}
return this.props.children
}
}
Portal(插槽)
優(yōu)點:(可用于彈框,對話框,tips組件)
1.樣式上使一些彈框組件直接放在根節(jié)點上,避免了z-index 錯亂的問題。
2.組件關系上保留了當前組件的關系
3.dom事件機制上,也保留了原來的關系,插槽子組件的事件可以傳給父組件。
const modalRoot = document.getElementById('modal-root');
// 插槽組件
class Modal extends Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
react 本身自帶類似與vue的slot功能(具名插槽)
class Layout extends Component {
render(){
return (
<div>
<h1>{this.props.head}</h1>
<h2>{this.props.body}</h2>
<h3>{this.props.foot}</h3>
</div>
)
}
}
<Layout head={<Header />} body={<Body />} foot={<Foot />}/>
react 中全局組件的方法。(見實例)
ReactDOM.render(React.createElement(GlobalAlert, props), targetDiv);
code spliting && 異步加載
1.路由級的異步加載
const OtherComponent = React.lazy(() => import('./OtherComponent'));
// 路由
<Route exact path="/render" component={OtherComponent} />
2.組件級的異步加載(原理是 componentDidCatch )
react.lazy() 配合 Suspense 組件,Suspense子樹中只要存在還沒回來的Lazy組件,就走 fallback 指定的內容。這樣可以 提升到任意祖先級的loading
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
3.組件內部的異步操作加載(Suspense的未來)
import React, {Suspense} from 'react';
import {unstable_createResource as createResource} from 'react-cache';
const getList = () => new Promise((resolve) => {
setTimeout(() => {
resolve('Morgan');
}, 1000);
})
const resource = createResource(getName);
const Greeting = () => {
return <div>hello {resource.read()}</div>
};
const SuspenseDemo = () => {
return (
<Suspense fallback={<div>loading...</div>} >
<Greeting />
</Suspense>
);
};
之前,只要有 AJAX 這樣的異步操作,就必須要用兩次渲染來顯示 AJAX 結果,這就需要用組件的 state 來存儲 AJAX 的結果,用 state 又意味著要把組件實現(xiàn)為一個 class??傊覀冃枰鲞@些:
- 實現(xiàn)一個 class;
- class 中需要有 state;
- 需要實現(xiàn) componentDidMount 函數(shù);
- render 必須要根據(jù)
this.state來渲染不同內容。
Suspense 被推出之后,可以極大地減少異步操作代碼的復雜度。不需要做上面這些雜事,只要一個函數(shù)形式組件就足夠了。
fiber 與 stack
老版本:https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html
新版本:https://claudiopro.github.io/react-fiber-vs-stack-demo/fiber.html
React.memo
》》React.memo() 和 PureComponent 很相似,它幫助我們控制何時重新渲染組件。
組件僅在它的 props 發(fā)生改變的時候進行重新渲染。通常來說,在組件樹中 React 組件,只要有變化就會走一遍渲染流程。但是通過 PureComponent 和 React.memo(),我們可以僅僅讓某些組件進行渲染。
React.memo(Child1);
Context
以前的用法:
// 父組件里面聲明: childContextTypes屬性 與 getChildContext方法
// 聲明Context對象屬性
static childContextTypes = {
propA: PropTypes.string,
methodA: PropTypes.func
}
// 返回Context對象,方法名是約定好的
getChildContext () {
return {
propA: 'propA',
methodA: () => 'methodA'
}
}
// 子組件里面聲明:
static contextTypes = {
propA: PropTypes.string
}
// 然后子組件就可以使用 this.context 獲取context屬性
現(xiàn)在的用法:
// 第一步:React.createContext創(chuàng)建一個context對象。
const ThemeContext = React.createContext('light');
// 父組件使用 provider
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 子組件使用 Consumer組件
function Child () {
return (
MyContext2.Consumer
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
)
}
// 或者在子組件中使用 static contextType = MyContext 然年子組件就可以使用 this.context
class MyClass extends React.Component {
static contextType = MyContext
componentDidMount() {
let value = this.context;
}
render() {
let value = this.context;
}
}
類似與redux的用法:
// 第一步:定義state 與 reducer
const Mycontext = React.createContext({
userName: 'zhenganlin',
changeUserName: () => {}
});
class Parent extends React.Component {
constructor(){
super()
this.state = {
context: {
userName: 'zhenganlin',
changeUserName: this.changeUserName
}
}
}
changeUserName = (name) => {
this.setState((preState) => {
context: Objec.assign({}, preState.contex, {name: name})
})
}
render() {
return (
<Mycontext.Provider value={this.state.context}>
<Toolbar />
</Mycontext.Provider>
);
}
}
class Child extends React.Component {
render() {
return (
<Mycontext.Consumer>
{
(contex) => { <button onClick={contex.changeUserName('小紅')}></button> }
}
</Mycontext.Consumer>
);
}
}
使用的注意事項:
// value 值不能使用字面量定義
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
refs 的用法與新功能
/* 之前的用法 */
Class Parent extends Component {
consturctor () {
super()
this.MyChild = null
}
render () {
<Child ref={ref => {this.MyChild = ref}}/>
}
}
/* 新的用法 */
Class Parent extends Component {
consturctor () {
super()
this.MyChild = React.createRef()
}
render () {
<Child ref={this.MyChild}/>
}
}
新增的 React.forwardRef (): 之前ref只能在父組件中拿到子組件的實例或者dom元素。通過React.forwardRef 可以跨層級拿到子組件內部的 實例或者dom
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>;
React Hooks
函數(shù)式組件與類組件的區(qū)別
1.類組件除了多出了生命周期、內部狀態(tài)state之外 與 函數(shù)式組件基本相同,沒有太大差別。
2.還有一個不同的地方是:類組件的props信息前置、函數(shù)組件的props信息后置。
3.函數(shù)組件可以理解成 class組件的 render 函數(shù)
state hook(讓函數(shù)組件擁有state)
import React, { useState } from 'react';
function Example(props) {
const [count, setCount] = useState(0);
const [name, changeName] = useState('小紅');
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me </button>
</div>
);
}
// **注意 useState 不能使用if條件語句
const Counter = () => {
const [count, setCount] = useState(0);
if (count % 2 === 0) {
const [foo, updateFoo] = useState('foo');
}
const [bar, updateBar] = useState('bar');
...
}
// 因為每次渲染 函數(shù)組件都會重新執(zhí)行,不能保存state。所以state是存在與React中的。只用第一次渲染的時候回初始化state的值,之后每次都是從React內存中取值。所以每一次 useState 調用對應內存記錄上一個位置,而且是按照順序來記錄的。React 不知道你把 useState 等 Hooks API 返回的結果賦值給什么變量,但是它也不需要知道,它只需要按照 useState 調用順序記錄就好了。
effect Hook(讓函數(shù)擁有生命周期)
//1. useEffect 每次渲染完(render)之后都會執(zhí)行。相當于 componentDidMount + componentDidUpdata + componentWillUnmount
// useEffect 可以有多個會依次執(zhí)行。
import { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.name = `Count: ${count}`;
});
useEffect(() => {
.......
document.title = `Count: ${count}`;
});
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
};
//2. useEffect(function(){}, [ 第二個參數(shù) ]) 的第二個參數(shù)如果發(fā)生變化才會,才會觸發(fā)該hook
// 所以可以通過參數(shù)設為常量來模擬componentDidMount只在第一渲染的時候執(zhí)行一次。
// 第二個參數(shù)也可以是pros或state。類似與 vue 中的 watch
useEffect(() => {
document.title = `Count: ${count}`;
}, [11111]);
//3. 區(qū)分 componentWillUnMount
useEffect(() => {
document.title = `Count: ${count}`;
// 一下代碼組件卸載階段才會執(zhí)行。
return function () {
websocket.close()
}
}, [123123]);
context hook
const ThemedPage = () => {
const theme = useContext(ThemeContext);
return (
<div>
<Header color={theme.color} />
<Content color={theme.color}/>
<Footer color={theme.color}/>
</div>
);
};
自定義hook
- 以use開頭2.里面用到了其他hook
import React, { useState, useEffect } from 'react';
// 自定義hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
// 使用自定義hook
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
// 總結: hook 的實現(xiàn)更加方便的實現(xiàn)了,不同組件之間的邏輯共享。
// 如果上面的內容用class實現(xiàn)
class XXX extend Component {
state = {
isOnline: false
}
handleStatusChange (xxx) {
this.setState({
isOnline: xxx
})
}
componentDidMount =()=> {
ChatAPI.subscribeToFriendStatus(friendID, this.handleStatusChange);
}
componentWillUnmount = () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, this.handleStatusChange);
}
render () {
XXXXXXX
}
}
針對這篇文章做了一些demo大家有興趣可翻翻
github地址: https://github.com/ganlinzhen/reactV16.git