react 16 、react hook 實戰(zhàn)探究

react 16出來這么久了,你工作中用了多少?。?!

REACT16 新特性總結

起因:(由于之前使用react導致好多用法還是保持原來的習慣,希望通過此次總結能將react16的新特性引用到實際工作中。)

生命周期

廢棄的周期:componentWillMount、componentWillReceiveProps、componentWillUpdate

新增周期:getDerivedStateFromProps、getSnapshotBeforeUpdate

react15的生命


react15生命周期.jpg

React16 的生命周期:


1652a030ed1506e0.jpeg
生命周期主要的變化 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??傊覀冃枰鲞@些:

  1. 實現(xiàn)一個 class;
  2. class 中需要有 state;
  3. 需要實現(xiàn) componentDidMount 函數(shù);
  4. 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
  1. 以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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容