React 初識

Reactjavascript

We built React to solve one problem: building large applications with data that changes over time.css

  • 聲明式的,用於構建用戶界面的 JavaScript 庫
  • 組合模型,using composition instead of inheritanc
  • 單向響應的數據流
  • JSX,語法糖,類型檢查,執行速度快(儘量減小與DOM直接操做的次數)

核心html

  • 組件
  • 虛擬DOM:解決jQuery操做真實DOM慢的問題
  • 響應式UI

普通的現代構建管道一般包括java

  • 包管理器(package manager):如npm或Yarn。它能夠利用大量的第三方軟件包生態系統,並輕鬆安裝或更新它們
  • 打包工具(bundler):如webpack或Browserify。它容許編寫模塊化代碼並將他們打包成爲一個小包,以實現加載性能的優化,節省加載時間
  • 編譯器(compiler):如Babel。它能夠在編寫現代JavaScript代碼的同時兼容舊版本瀏覽器

經常使用庫概覽node

react.js:React的核心庫
react-dom.js:提供與DOM相關的操做功能
Browser.js:將JSX語法轉爲JavaScript語法(耗時)

元素

Elements are the smallest building blocks of React apps.react

 

組件

Small and Isolated pieces of code,模版即組件,組件即HTML自定義標籤,是包含了模板代碼的一種特殊的HTML標籤類型。webpack

  • 函數式組件
  • 類組件

當組件第一次渲染到DOM時,在React中稱爲掛載(mounting);當組件產生的DOM被銷燬時,在React中稱爲卸載(unmounting)。git

全部React組件都必須是純函數,並禁止修改其自身props 。在JSX回調中必須注意this的指向,提供3種方法:github

  • 在構造函數中顯式綁定:.bind(this)
  • 使用箭頭函數:onClick={(e) => this.handleClick(e)}
  • 保持使用ES5風格:createReactClass

推薦第1種。箭頭函數每次渲染時都建立一個不一樣的回調。多數狀況下沒問題,然而若是這個回調被做爲prop(屬性)傳遞給下級組件,這些組件可能須要額外的重複渲染。web

setState

  • (多是)異步執行
  • 構造函數是惟一能夠初始化 this.state 的地方
// ok,當前值不依賴上一次的值
this.setState({name: 'Hello'});

// 提供callback方式1
this.setState((prevState, props) => { 
  return { ...prevState, name: props.name }; 
});
// 或
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));
// 或
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

// 提供callback方式2
this.setState({
  name: 'qwer'
  }, ()=>{
      console.log(this.state.name); //qwer
});

在狀態更新、渲染完成後,會觸發對回調函數的執行。有關信息參見:react - setState

狀態提高(Lifting State Up)

state建立有2種:

  1. 當組件以class類建立,state能夠是class的屬性值,也能夠在構造函數經過this.state賦值來建立
  2. 當組件以函數建立,state需經過函數的getIntialState函數的返回值來建立

在React中,共享state(狀態)是經過將其移動到須要它的組件的最接近的共同祖先組件來實現,使React的state成爲 「單一數據源原則」 。
在一個React應用中,對於任何可變的數據都應該循序「單一數據源」原則,依賴從上向下的數據流向。
對於UI中的錯誤,使用React開發者工具來檢查props,向上遍歷樹,直到找到負責更新狀態的組件,跟蹤到bug的源頭。
具體參見:React Developer Tools

將參數傳遞給事件處理程序

  • arrow functions:箭頭函數方式,參數e做爲React事件對象做爲第二個參數進行顯式傳遞
  • Function.prototype.bind:bind方式,事件對象以及更多的參數將會被隱式傳遞
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

對於第2種方法,方法定義的形式

deleteRow(id, event) {...}  

當須要從子組件中更新父組件的state時,要在父組件建立事件句柄 (handleChange) ,並做爲prop (updateStateProp) 傳遞到子組件。

var Content = React.createClass({
  render: function() {
    return  <div>
                <button onClick = {this.props.updateStateProp}>點我</button>
                <h4>{this.props.myDataProp}</h4>
              </div>
  }
});
var HelloMessage = React.createClass({
  getInitialState: function() {
    return {value: 'Hello Runoob!'};
  },
  handleChange: function(event) {
    this.setState({value: '菜鳥教程'})
  },
  render: function() {
    var value = this.state.value;
    return <div>
              <Content myDataProp = {value} 
                 updateStateProp = {this.handleChange}></Content>
              </div>;
  }
});
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

綜上,對於顯式bind方式,通式以下

handleclick(要傳的參數,event){...} 
onClick = {this.handleclick.bind(this,要傳的參數)}

組件鉤子函數生命週期

。。。

高階組件

高階組件是一個函數,接受一個組件並返回一個新的組件

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 高階組件既不會修改輸入組件,也不會經過繼承來複制行爲。
  • 經過包裹的形式,高階組件將原先的組件組合在容器組件中。
  • 高階組件是純函數,沒有反作用。
  • 高階組件最好是經過將輸入組件包裹在容器組件的方式來使用組合。

錯誤邊界

Error Boundaries 是React組件,它能夠在子組件樹的任何位置捕獲JavaScript錯誤、記錄這些錯誤,並顯示一個備用UI,而不是使整個組件樹崩潰。可是,僅能夠捕獲其子組件的錯誤,沒法捕獲其自身的錯誤。對如下狀況無能爲力:

  • 事件處理:事件處理器內部的錯誤,採用try{}catch(){}捕獲便可
  • 異步代碼(例如 setTimeout 或 requestAnimationFrame 回調函數)
  • 服務端渲染
  • 錯誤邊界自身拋出來的錯誤(而不是其子組件)

鉤子函數:componentDidCatch(error, info)
相似JS的catch(){}方法,若是一個錯誤邊界沒法渲染錯誤信息,則錯誤會向上冒泡至最接近的錯誤邊界。

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>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}   

ref屬性

用來綁定到render()輸出的任何組件上,容許引用render()返回的相應的支撐實例(Backing Instance)。

簡言之,用於從組件獲取真實的DOM結點。

  • 處理focus、文本選擇或者媒體播放
  • 觸發強制動畫
  • 集成第三方DOM庫
  • 訪問<input type="file">表單要提交處理的文件

組件並非真實的DOM節點,而是存在於內存之中的一種數據結構,叫作虛擬DOM (virtual DOM)。只有當它插入文檔之後,纔會變成真實的DOM 。根據 React 的設計,全部的DOM變更,都先在虛擬DOM上發生,而後再將實際發生變更的部分,反映在真實DOM上,這種算法叫作 DOM diff,能夠極大提升網頁的性能表現。

場景:獲取組件new出來的實例,繞過父子組件通訊的約束,直接操做組件new出來的實例

  • 經過 ref 屬性獲取真實的React元素實例
  • 經過 ReactDOM.findDOMNode(組件真實實例) 返回真實的DOM結構

經過ref屬性獲取到React組件new出來的真實實例,前置條件:

  • 自定義組件
  • 經過class來定義

真實DOM結點結構 = ReactDOM.findDOMNode(組件真實實例)

this.props.children屬性

表示組件的全部子結點,其值有三種可能:

  • undefined:當前組件沒有子節點
  • object:有且僅有有一個子節點
  • array:有多個子節點

React提供工具方法: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)

條件渲染組件

To do this return null instead of its render output:從組件的render方法返回null不會影響組件生命週期方法的觸發。

function WarningBanner(props) {
  if (!props.warnsInfoMsg) {
    return null;
  }

  return (
    <div className="warning">
      Warning! {this.props.warnsInfoMsg}
    </div>
  );
}

模塊加載機制

CommonJS規範

  • fs:文件系統模塊,負責讀寫文件,支持stream和pipe(自動流式讀寫)
  • http:web服務器模塊,提供request和response對象分別封裝http請求和響應  
  • cypto:加密解密模塊,哈希算法,數字證書
// 模塊對外提供變量或函數方法
module.exports = {
    key: value
    ...
};
// 引入模塊
const module = require('./Module.js');

context

上下文,提供經過組件樹傳遞數據的方法,在組件間共享數據,避免經過中間元素傳遞 props。

  • 使用props參數傳遞方式
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}
  • 使用context:Stick to cases where the same data needs to be accessed in many components at multiple levels.
// 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, 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>
  );
}

function ThemedButton(props) {
  // Use a Consumer 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".
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

其中,ThemeContext 是 {Provider, Consumer} 對象

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

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

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

仔細體會,該方式相似發佈訂閱模式,provider生產數據,Consumer消費數據。 

具體參見:Context - React

編程思想

頁面設計流程

  1. 組件拆解:自下而上,由內而外,單一職責原則
  2. 組件組合:各組件組合成靜態頁面框架
  3. 肯定UI狀態(state)的最小但完整集合表示
  4. 肯定state位置:狀態提高,公共父級組件
  5. 組件通訊交互:回調函數(反向數據流)
    • 父組件將方法名做爲參數傳遞給子組件
    • 在子組件上觸發的事件調用父組件的方法以更新父組件的狀態

注:不要在子組件上直接調用父組件方法。子組件應該封裝一個事件句柄,在句柄內調用回調函數。

關於肯定 UI state

  • 是否經過props(屬性)從父級傳入? 如果,它可能不是state(狀態)。
  • 是否永遠不會發生變化? 如果,它可能不是state(狀態)。
  • 是否能夠由組件中其餘的state(狀態)或props(屬性)計算得出?如果,則它不是state(狀態)。

具體參見:React 編程思想

學習了下React 核心開發者出品的 React 設計思想 ,簡單總結以下:

  • 變換(Transformation):純函數

  • 抽象(Abstraction):函數/組件調用

  • 組合(Composition):組合思想,而非繼承

  • 狀態(State):不可變性,setState()

  • Memoization:記憶緩存

  • 列表(Lists)

  • 連續性(Continuations)

  • 代數效應(Algebraic Effects):context

經驗避坑

關於React註釋

  • 在標籤外的的註釋不能使用花括號
  • 在標籤內部的註釋須要花括號
ReactDOM.render(
    /*註釋 */
    <h1>xxxx {/*註釋*/}</h1>,
    document.getElementById('root')
);

關於Html與React

  • 在React的.js文件中,標籤中屬性和事件必須駝峯格式
  • 爲組件添加屬性時,原生Html中的關鍵字class和for必須寫成className和htmlFor 

環境判斷

根據瀏覽器和Node環境提供的全局變量名稱來判斷當前環境:

if (typeof(window) === 'undefined') {
    console.log('node.js');
} else {
    console.log('browser');
}

關於規範

  • 添加屬性Object.assign() 
  • 擴展運算符(...)拷貝數組  
  • Array.from():將類數組對象轉爲數組  
  • 全部配置項都應該集中在1個對象中,做爲最後1個參數,布爾值不能夠直接做爲參數。  
  • 使用帕斯卡式命名構造函數或類 

具體參見:Airbnb React/JSX Style Guide;  

參考

React 官網 - EngReact 中文文檔

React 入門實例教程 -阮一峯

相關文章
相關標籤/搜索