【學習筆記】React

JSX

是什麼?

JSX是JavaScript的語法擴展,用來描述用戶界面javascript

使用規範

  1. JSX語句自己也是表達式,在JS代碼中能夠做爲普通表達式使用
  2. JSX語句中引號包裹字符串,大括號包裹JS表達式
  3. JSX語言特性沿襲JS特性,所以屬性名使用駝峯式規範
  4. JSX默認進行防注入攻擊,渲染前會過濾全部傳入值,而且將全部內容都轉爲字符串

擴展

模塊

因爲 JSX 編譯後會調用 React.createElement 方法,因此在包含JSX的模塊中必須先引入React 模塊html

標籤名

  1. 內置組件標籤名以小寫字母開頭,如<div><span>。自定義組件標籤名以大寫字母開頭
  2. 標籤名可使用點表示法 <MyComponents.DatePicker color="blue" />
  3. 標籤名不能爲表達式java

    function Story(props) {
      // 錯誤!JSX 標籤名不能爲一個表達式。
      return <components[props.storyType] story={props.story} />;
    }
    
    function Story(props) {
      // 正確!JSX 標籤名能夠爲大寫開頭的變量。
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    }

屬性

  1. 若是你沒有給屬性傳值,它默認爲 true
  2. 若是已經有一個props對象,可使用擴展運算符傳遞整個屬性對象

子代

  1. 子代能夠是字符串常量,JSX,JS表達式,函數
  2. 布爾值、Null 和 Undefined 被忽略

本質

Babel轉譯器會將JSX轉譯爲React.createElement方法,該方法首先會進行一些避免bug的檢查,以後返回React元素(一個JavaScript對象)。JSX 只是爲 React.createElement(component, props, ...children) 方法提供的語法糖。react

React元素渲染

渲染過程

React DOM 使 React 元素 和 瀏覽器 DOM 的數據內容保持一致npm

經過ReactDOM.render() 將React元素渲染到頁面上json

更新元素

React元素是一個不可變的(immutable),元素被建立以後沒法改變其內容和屬性數組

若要更新,建立一個新的React元素,從新調用ReactDOM.render()渲染 瀏覽器

React DOM 首先會比較元素內容前後的不一樣,而在渲染過程當中只會更新改變了的部分性能優化

組件

是什麼?

獨立的、可複用的部件服務器

接收任意的輸入值(稱之爲「props」),並返回一個React元素。

定義方法

函數定義

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

類定義

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Props

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

ReactDOM.render(
  <Welcome name="Sara" />,
  document.getElementById('root')
);

調用自定義組件,組件上的屬性集合到props對象上傳遞給組件

全部的React組件必須像純函數(輸入值不會被改變,輸入值相同總會輸出相同的結果)那樣使用它們的props。

PropTypes

類型檢查

PropTypes 包含一整套驗證器,可用於確保你接收的數據是有效的。

當你給屬性傳遞了無效值時,JavsScript 控制檯將會打印警告。出於性能緣由,propTypes 只在開發模式下進行檢查。

注意: React.PropTypes 自 React v15.5 起已棄用。請使用 prop-types 庫代替。

defaultProps

屬性默認值

類型檢查發生在 defaultProps 賦值以後,因此類型檢查也會應用在 defaultProps 上面。

State & 生命週期

State

狀態,組件內部維護

  • 構造函數是惟一可以初始化this.state的地方
  • this.propsthis.state的更新多是異步的,React 能夠將多個setState() 調用合併成一個調用來提升性能。

    // Wrong
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    
    // Correct
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));
  • setState()是一個請求

    // 不依賴state計算時書寫
    // callback在setState執行完成同時組件被重渲以後執行,等同於componentDidUpdate的做用
    this.setState({
        counter: 2
    }, [callback]);
    
    // 依賴state以前的狀態
    this.setState((prevState) => {
      return {counter: prevState.quantity + 1};
    });
  • 單向數據流,經過state維護組件內部狀態,經過props向子組件傳遞數據

生命週期

  • defaultProps, propTypes
  • constructor() - 構造函數是初始化狀態的合適位置。
  • UNSAFE_componentWillMount() - 裝配發生前調用,在這方法裏同步地設置狀態將不會觸發重渲
  • render() - 渲染時調用
  • componentDidMount() - 組件裝配完成時調用,初始化DOM節點,發送網絡請求,事件訂閱
  • UNSAFE_componentWillReceiveProps(nextProps) - 裝配了的組件接收到新屬性前調用,即便屬性未有任何改變,也可能會調用該方法,請確保比較先後值
  • shouldComponentUpdate(nextProps, nextState) - 當接收到新屬性或狀態時在渲染前調用,該方法並不會在初始化渲染或當使用forceUpdate()時被調用
  • UNSAFE_componentWillUpdate() - 接收到新屬性或狀態時時調用,不能在這調用this.setState()
  • getDerivedStateFromProps(nextProps, prevState) - 組件實例化後和接受新屬性時調用,調用this.setState() 一般不會觸發,返回響應屬性
  • getSnapshotBeforeUpdate(prevProps, prevState) - 在最新的渲染輸出提交給DOM前將會當即調用,這一輩子命週期返回的任何值將會 做爲參數被傳遞給componentDidUpdate()
  • componentDidUpdate(prevProps, prevState) - 更新發生後當即調用,不會在初始化渲染時調用。操做DOM,發送請求
  • componentWillUnmount() - 組件被卸載和銷燬以前馬上調用。清理定時器、請求、DOM節點、事件訂閱

事件處理

this

類的方法默認不會綁定this,解決辦法:

  • 在構造函數中使用bind綁定
  • 使用實驗性的屬性初始化器

    class LoggingButton extends React.Component {
      // This syntax ensures `this` is bound within handleClick.
      // Warning: this is *experimental* syntax.
      handleClick = () => {
        console.log('this is:', this);
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            Click me
          </button>
        );
      }
    }
  • 使用箭頭函數定義回調函數

    class LoggingButton extends React.Component {
      handleClick() {
        console.log('this is:', this);
      }
    
      render() {
        // This syntax ensures `this` is bound within handleClick
        return (
          <button onClick={(e) => this.handleClick(e)}>
            Click me
          </button>
        );
      }
    }

    每次渲染時會建立一個不一樣的回調函數,若是回調函數做爲一個屬性值傳入低階組件,會致使額外渲染

event

React封裝的一個合成事件SyntheticEvent

事件處理函數返回false不會再阻止事件傳播, 因此必須得手動觸發e.stopPropagation()e.preventDefault() 方法。

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

傳遞參數

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'};
    }
    
    preventPop(name, e){    //事件對象e要放在最後
        e.preventDefault();
        alert(name);
    }
    
    render(){
        return (
            <div>
                <p>hello</p>
                {/* Pass params via bind() method. */}
                <a onClick={this.preventPop.bind(this, this.state.name)}>Click</a>
                <a onClick={(e) => {this.preventPop(this.state.name, e)}}>Click</a>
            </div>
        );
    }
}

條件渲染

在render函數中使用條件語句實現條件渲染,可使用變量來儲存元素

在JSX中使用條件運算符組成表達式實現條件渲染

render 方法返回 null隱藏組件

列表

經過使用{}在JSX內構建一個元素集合

key

每一個列表元素須要有key屬性

keys能夠在DOM中的某些元素被增長或刪除的時候幫助React識別哪些元素髮生了變化

key會做爲給React的提示,但不會傳遞給你的組件

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

表單

受控組件

經過onChange+setState控制狀態改變,使React控制組件狀態

<input type="text" value={this.state.value} onChange={this.handleChange} />
<textarea value={this.state.value} onChange={this.handleChange} />
<select value={this.state.value} onChange={this.handleChange}>
    <option value="grapefruit">Grapefruit</option>
    <option value="lime">Lime</option>
    <option value="coconut">Coconut</option>
    <option value="mango">Mango</option>
</select>

當你有處理多個受控的input元素時,你能夠經過給每一個元素添加一個name屬性,來讓處理函數根據 event.target.name的值來選擇作什麼。

非受控組件

經過ref獲取獲取DOM

在 React 的生命週期中,表單元素上的 value 屬性將會覆蓋 DOM 中的值

使用defaultValue指定初始值

狀態提高

使用 react 常常會遇到幾個組件須要共用狀態數據的狀況。

這種狀況下,咱們最好將這部分共享的狀態提高至他們最近的父組件當中進行管理。

組合 vs 繼承

經過子代props.children或者自定義屬性承載組件

使用組合實現包含關係,動態定義傳入容器的子組件

使用組合實現特殊實例,替換繼承的做用,好比說Prom

React理念

  • 組件按單一功能封裝
  • 設定最小可變狀態集,要點是 DRY:不要重複(Don’t Repeat Yourself)。找出應用程序的絕對最小表示並計算你所須要的其餘任何請求。例如,若是你正在建立一個 TODO 列表,只要保存一個包含 TODO 事項的數組;不要爲計數保留一個單獨的狀態變量。相反,當你想要渲染 TODO 計數時,只須要使用 TODO 數組的長度就能夠了。

Refs & DOM

React v16.3

  1. 建立ref React.createRef()
  2. 關聯ref,可使用直接關聯也可以使用回調
class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 建立 ref 存儲 textInput DOM 元素
    this.textInput = React.createRef();
    this.buttonInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
    this.setTextInputRef = element => {
      this.buttonInput = element;
    };
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 輸入框得到焦點
    // 注意:經過 "current" 取得 DOM 節點
    this.textInput.current.focus();
  }

  render() {
    // 告訴 React 咱們想把 <input> ref 關聯到構造器裏建立的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />

          
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
          ref={this.setTextInputRef}
        />
      </div>
    );
  }
}

ref 的更新會發生在componentDidMountcomponentDidUpdate 生命週期鉤子以前。

性能優化

使用生產版本

開發模式下React會更大更慢,所以必定要用生產版本部署

使用 Chrome Performance 歸檔組件

  1. 在項目地址欄內添加查詢字符串 ?react_perf(例如, http://localhost:3000/?react_perf)。
  2. 打開Chrome開發工具Performance 標籤頁點擊Record.
  3. 執行你想要分析的動做。不要記錄超過20s,否則Chrome可能會掛起。
  4. 中止記錄。
  5. React事件將會被歸類在 User Timing標籤下。

避免重複渲染

使用SCU控制

避免數據突變

支持ES6的狀況下,使用Object.assign/擴展運算符返回新數據,防止引用類型數據突變

使用Immutable.js保持數據不可變,下降出錯率

Reconciliation

  1. 兩個不一樣類型的元素將產生不一樣的樹。
  2. 經過渲染器附帶key屬性,開發者能夠示意哪些子元素多是穩定的。

Context

共享那些被認爲對於一個組件樹而言是「全局」的數據,例如當前認證的用戶、主題或首選語言。

不要僅僅爲了不在幾個層級下的組件傳遞 props 而使用 context,它是被用於在多個層級的多個組件須要訪問相同數據的情景。

const {Provider, Consumer} = React.createContext(defaultValue);

<Provider value={/* some value */}>

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

每當Provider的值發送改變時, 做爲Provider後代的全部Consumers都會從新渲染。 從Provider到其後代的Consumers傳播不受shouldComponentUpdate方法的約束,所以即便祖先組件退出更新時,後代Consumer也會被更新。

Fragments

聚合一個子元素列表,代替組件根元素使用

<></><React.Fragment/> 的語法糖。

<></> 語法不能接受鍵值或屬性,要添加key屬性須要使用<React.Fragment/>

Portals

Portals 提供了一種將子節點渲染到父組件之外的 DOM 節點的方式。

ReactDOM.createPortal(child, container)
// child 可渲染的React子元素
// container DOM元素

雖然DOM樹中的位置平行,但React樹中組件仍在根組件樹中,樹組件仍能捕獲到事件

錯誤邊界

是什麼?

錯誤邊界組件用於捕獲其子組件樹 JavaScript 異常,記錄錯誤並展現一個回退的 UI

錯誤邊界在渲染期間,生命週期方法內,以及整個組件樹構造函數內捕獲錯誤。

錯誤邊界沒法捕獲事件處理函數內部的錯誤,事件處理函數內部的錯誤使用try...catch捕獲。

錯誤邊界沒法捕獲異步事件的錯誤,異步事件的錯誤在回調函數內部自行處理。

錯誤邊界沒法捕獲服務器端渲染和錯誤邊界自身拋出的錯誤。

自 React 16 開始,任何未被錯誤邊界捕獲的錯誤將會卸載整個 React 組件樹。

組件內部

若是一個類組件定義了 componentDidCatch(error, info): 方法,則該組件是一個錯誤邊界組件。

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

  componentDidCatch(error, info) {
    // error: 錯誤信息
    // info: 組件棧追蹤
    this.setState({ hasError: true });
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

組件使用

錯誤邊界的粒度徹底取決於你的應用。你能夠將其包裝在最頂層的路由組件併爲用戶展現一個 「發生異常(Something went wrong)「的錯誤信息,就像服務端框架一般處理崩潰同樣。你也能夠將單獨的插件包裝在錯誤邊界內部以保護應用不受該組件崩潰的影響。

Web Components

Web Components是W3C規範的新功能,提供自定義元素封裝功能。至關於在DOM的層面封裝一個自定義元素,元素的展現和特性在Web Components內部實現。在React眼中,一個Web Component和一個普通元素無異。

React組件是React層面的元素單元,封裝有UI、邏輯、數據響應。

高階組件(HOC)

是什麼?

對組件邏輯進行重用的一種抽象模式

高階組件就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件

const EnhancedComponent = higherOrderComponent(WrappedComponent);

經過將原組件 包裹 在容器組件裏面的方式來 組合 使用原組件。高階組件就是一個沒有反作用的純函數。

需避免的問題

  • 請使用組合性高階組件,避免使用更改性高階組件

    在高階組件內部修改(或以其它方式修改)原組件的原型屬性,稱爲更改性高階組件

    function logProps(InputComponent) {
      InputComponent.prototype.componentWillReceiveProps(nextProps) {
        console.log('Current props: ', this.props);
        console.log('Next props: ', nextProps);
      }
      // 咱們返回的原始組件實際上已經
      // 被修改了。
      return InputComponent;
    }
    
    // EnhancedComponent會記錄下全部的props屬性
    const EnhancedComponent = logProps(InputComponent);

    在高階組件內部使用組合返回一個新的組件,稱爲組合型高階組件

    function logProps(WrappedComponent) {
      return class extends React.Component {
        componentWillReceiveProps(nextProps) {
          console.log('Current props: ', this.props);
          console.log('Next props: ', nextProps);
        }
        render() {
          // 用容器組件組合包裹組件且不修改包裹組件,這纔是正確的打開方式。
          return <WrappedComponent {...this.props} />;
        }
      }
    }
  • 高階組件應該傳遞與它要實現的功能點無關的props屬性

    render() {
      // 過濾掉與高階函數功能相關的props屬性,
      // 再也不傳遞
      const { extraProp, ...passThroughProps } = this.props;
    
      // 向包裹組件注入props屬性,通常都是高階組件的state狀態
      // 或實例方法
      const injectedProp = someStateOrInstanceMethod;
    
      // 向包裹組件傳遞props屬性
      return (
        <WrappedComponent
          injectedProp={injectedProp}
          {...passThroughProps}
        />
      );
    }
  • 最大化使用組合

    const ConnectedComment = connect(commentSelector, commentActions)(Comment);

    connect函數返回一個高階組件

    使用compose代替高階組件嵌套使用

    const enhance = compose(
      // 這些都是單參數的高階組件
      withRouter,
      connect(commentSelector)
    )
    const EnhancedComponent = enhance(WrappedComponent)
  • 不要在render()中調用高階函數
  • 將靜態方法作拷貝,當使用高階組件包裝組件,原始組件被容器組件包裹,也就意味着新組件會丟失原始組件的全部靜態方法。使用hoistNonReactStatic處理
  • 高階組件返回組件命名

    function withSubscription(WrappedComponent) {
      class WithSubscription extends React.Component {/* ... */}
      WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
      return WithSubscription;
    }
    
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
  • refs不會被傳遞到被包裹組件,React16.3版本中加入React.forwardRef解決這個問題

    function logProps(Component) {
      class LogProps extends React.Component {
        componentDidUpdate(prevProps) {
          console.log('old props:', prevProps);
          console.log('new props:', this.props);
        }
    
        render() {
          const {forwardedRef, ...rest} = this.props;
    
          // Assign the custom prop "forwardedRef" as a ref
          return <Component ref={forwardedRef} {...rest} />;
        }
      }
    
      // Note the second param "ref" provided by React.forwardRef.
      // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
      // And it can then be attached to the Component.
      function forwardRef(props, ref) {
        return <LogProps {...props} forwardedRef={ref} />;
      }
    
      // These next lines are not necessary,
      // But they do give the component a better display name in DevTools,
      // e.g. "ForwardRef(logProps(MyComponent))"
      const name = Component.displayName || Component.name;
      forwardRef.displayName = `logProps(${name})`;
    
      return React.forwardRef(forwardRef);
    }

Render Props

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

是什麼?

一個函數屬性,用於渲染

屬性名不必定是render,重要的是它起到的做用

注意

  • 在使用時最好指定propType
  • props檢查優化,直接使用函數在render時會返回新函數;能夠將函數定義爲實例方法使用,從而避免這一問題

與第三方庫協同

與jQuery協同,把DOM操做交給jQuery,React數據驅動渲染與jQuery DOM分離

可訪問性

  • JSX支持全部的aria-* HTML屬性
  • JSX中for特性被寫做htmlFor

代碼分割

import()動態導入 + react-loadable模塊

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Loadable from 'react-loadable';

const Loading = () => <div>Loading...</div>;

const Home = Loadable({
  loader: () => import('./routes/Home'),
  loading: Loading,
});

const About = Loadable({
  loader: () => import('./routes/About'),
  loading: Loading,
});

const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
    </Switch>
  </Router>
);

API

createElement()

React.createElement(
  type,
  [props],
  [...children]
)

cloneElement()

React.cloneElement(
  element,
  [props],
  [...children]
)

element 做爲起點,克隆並返回一個新的React元素(React Element)。生成的元素將會擁有原始元素props與新props的淺合併。新的子級會替換現有的子級。來自原始元素的 keyref將會保留。

isValidElement()

驗證對象是不是一個React元素。返回 truefalse

React.Children

React.Children 提供了處理 this.props.children 這個數據結構的工具。

React.Children.map(children, function[(thisArg)])
React.Children.forEach(children, function[(thisArg)])
React.Children.count(children)
React.Children.only(children)
React.Children.toArray(children)

ReactDOM

import ReactDOM from 'react-dom'

ReactDOM.render(
  element,
  container,
  [callback]
)
ReactDOM.findDOMNode(component)
相關文章
相關標籤/搜索