React基礎篇2

小圖子發表於2019-02-28

1. React生命週期

class Counter extends React.Component{ // 他會比較兩個狀態相等就不會重新整理檢視 PureComponent是淺比較
  static defaultProps = {
    name:'李四'
  };
  constructor(props){
    super();
    this.state = {number:0}
    console.log('1.constructor建構函式')
  }
  componentWillMount(){ // 取本地的資料 同步的方式:採用渲染之前獲取資料,只渲染一次
    console.log('2.元件將要載入 componentWillMount');
  }
  componentDidMount(){
    console.log('4.元件掛載完成 componentDidMount');
  }
  handleClick=()=>{
    this.setState({number:this.state.number+1});
  };
  // react可以shouldComponentUpdate方法中優化 PureComponent 可以幫我們做這件事
  shouldComponentUpdate(nextProps,nextState){ // 代表的是下一次的屬性 和 下一次的狀態
    console.log('5.元件是否更新 shouldComponentUpdate');
    return nextState.number%2;
    // return nextState.number!==this.state.number; //如果此函式種返回了false 就不會呼叫render方法了
  } //不要隨便用setState 可能會死迴圈
  componentWillUpdate(){
    console.log('6.元件將要更新 componentWillUpdate');
  }
  componentDidUpdate(){
    console.log('7.元件完成更新 componentDidUpdate');
  }
  render(){
    console.log('3.render');
    return (
      <div>
        <p>{this.state.number}</p>
        {this.state.number>3?null:<ChildCounter n={this.state.number}/>}
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}
class ChildCounter extends Component{
  componentWillUnmount(){
    console.log('元件將要解除安裝componentWillUnmount')
  }
  componentWillMount(){
    console.log('child componentWillMount')
  }
  render(){
    console.log('child-render')
    return (<div>
      {this.props.n}
    </div>)
  }
  componentDidMount(){
    console.log('child componentDidMount')
  }
  componentWillReceiveProps(newProps){ // 第一次不會執行,之後屬性更新時才會執行
    console.log('child componentWillReceiveProps')
  }
  shouldComponentUpdate(nextProps,nextState){
    return nextProps.n%3; //子元件判斷接收的屬性 是否滿足更新條件 為true則更新
  }
}
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 狀態更新會觸發的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 屬性更新
// componentWillReceiveProps newProps
// 解除安裝
// componentWillUnmount
複製程式碼

Alt

2. 使用 PropTypes 進行型別檢查

React 內建了型別檢測的功能。要在元件中進行型別檢測,你可以賦值 propTypes 屬性

  • array 陣列
  • bool 布林值
  • func 函式
  • number 數字
  • object 物件
  • string 字串
  • symbol 符號
  • node 任何東西都可以被渲染:numbers, strings, elements,或者是包含這些型別的陣列(或者是片段)。
  • element React元素
  • instanceOf(Message) 類的一個例項
  • oneOf(['News', 'Photos']) 列舉值
  • oneOfType([PropTypes.string,PropTypes.number,PropTypes.instanceOf(Message)]) 多種型別其中之一
  • arrayOf(PropTypes.number) 某種型別的陣列
  • objectOf(PropTypes.number) 某種型別的物件
  • shape({color: PropTypes.string,fontSize: PropTypes.number}) 特定形式的物件
  • func.isRequired 可以使用 `isRequired' 連結上述任何一個,以確保在沒有提供 prop 的情況下顯示警告
  • any.isRequired 任何資料型別的值 function(props, propName, componentName) { return new Error()} 自定義的驗證器
  • arrayOf(function(propValue, key, componentName, location, propFullName) {}
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Person extends React.Component{
  static defaultProps = {
    name:'Stranger'
  }
    static propTypes={
        name: PropTypes.string.isRequired,
        age: PropTypes.number.isRequired,
        gender: PropTypes.oneOf(['male','famale']),
        hobby: PropTypes.array,
        postion: PropTypes.shape({
            x: PropTypes.number,
            y:PropTypes.number
        }),
        age(props,propName,componentName) {
            let age=props[propName];
            if (age <0 || age>120) {
                return new Error(`Invalid Prop ${propName} supplied to ${componentName}`)
            }
        }
    }
    render() {
        let {name,age,gender,hobby,position}=this.props;
        return (
            <table>
                <thead>
                <tr>
                    <td>姓名</td>
                    <td>年齡</td>
                    <td>性別</td>
                    <td>愛好</td>
                    <td>位置</td>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>{name}</td>
                    <td>{age}</td>
                    <td>{gender}</td>
                    <td>{hobby.join(',')}</td>
                    <td>{position.x+' '+position.y}</td>
                </tr>
                </tbody>
            </table>
        )
    }
}
let person={
    age: 100,
    gender:'male',
    hobby: ['basketball','football'],
    position: {x: 10,y: 10},
}
ReactDOM.render(<Person {...person}/>, document.getElementById('root'));

複製程式碼

3. 優化效能

在內部,React使用幾種巧妙的技術來最大限度地減少更新UI所需的昂貴的 DOM 操作的數量

3.1 使用生產版本

  • 最好在開發應用時使用開發模式,部署應用時換為生產模式
Create React App
npm run build
複製程式碼

單檔案構建

<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
複製程式碼

webpack

new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()
複製程式碼

4. 使用 Chrome 效能分析工具 分析元件效能

1.通過新增 ?react_perf 查詢欄位載入你的應用(例如:http://localhost:3000/?react_perf)。 2.開啟 Chrome DevTools Performance 並點選 Record 。timeline-tool 3.執行你想要分析的操作,不要超過20秒,否則 Chrome 可能會掛起。 4.停止記錄。 5.在 User Timing 標籤下,React事件將會分組列出。react-16

5. 避免重新渲染 React 構建並維護渲染 UI 的內部表示 當元件的 props 和 state 改變時,React 通過比較新返回的元素 和 之前渲染的元素 來決定是否有必要更新DOM元素。當二者不相等時,則更新 DOM 元素

Alt

5.1 shouldComponentUpdate

5.2 React.PureComponent 與 React.Component 完全相同,但是在shouldComponentUpdate()中實現時,使用了 props 和 state 的淺比較

5.3 使用 Immutable 資料結構

  • 不可變(Immutable): 一個集合一旦建立,在其他時間是不可更改的。
  • 持久的(Persistent): 新的集合可以基於之前的結合建立併產生突變,例如:set。原來的集合在新集合建立之後仍然是可用的。
  • 結構共享(Structural Sharing): 新的集合儘可能通過之前集合相同的結構建立,最小程度地減少複製操作來提高效能。
import { is } from 'immutable';
shouldComponentUpdate: (nextProps, nextState) => {
return !(this.props === nextProps || is(this.props, nextProps)) ||
       !(this.state === nextState || is(this.state, nextState));
}
複製程式碼

改進setState

this.setState({ data: this.state.data.update('counter', counter => counter + 1) });
複製程式碼

6. Reconciliation

當比較不同的兩個樹,React 首先比較兩個根元素。根據根跟的型別不同,它有不同的行為

  • 當根元素型別不同時,React 將會銷燬原先的樹並重寫構建新的樹
  • 當比較兩個相同型別的 React DOM 元素時,React 檢查它們的屬性(attributes),保留相同的底層 DOM 節點,只更新發生改變的屬性(attributes)
  • 當一個元件更新的時候,元件例項保持不變,以便在渲染中保持state。React會更新元件例項的屬性來匹配新的元素,並在元素例項上呼叫 componentWillReceiveProps() 和 componentWillUpdate()
  • Keys

7. 上下文(Context)

在某些場景下,你想在整個元件樹中傳遞資料,但卻不想手動地在每一層傳遞屬性。你可以直接在 React 中使用強大的”context” API解決上述問題

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
    render() {
        return (
            <div>
                <Title/>
            </div>
        )
    }
}
class Title extends Component{
    static contextTypes={
        color:PropTypes.string
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Title
            </div>
        )
    }
}
class Main extends Component{
    render() {
        return (
            <div>
                <Content>
                </Content>
            </div>
        )
    }
}
class Content extends Component{
    static contextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Content
                <button onClick={()=>this.context.changeColor('green')}>綠色</button>
                <button onClick={()=>this.context.changeColor('orange')}>橙色</button>
            </div>
        )
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={color:'red'};
    }
    static childContextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            changeColor:(color)=>{
                this.setState({color})
            }
        }
    }
    render() {
        return (
            <div>
                <Header/>
                <Main/>
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
複製程式碼

8. 片段(fragments) React 中一個常見模式是為一個元件返回多個元素。 片段(fragments) 可以讓你將子元素列表新增到一個分組中,並且不會在DOM中增加額外節點。

import React from 'react';
import ReactDOM from 'react-dom';

class List extends React.Component{
    render() {
        return (
            <React.Fragment>
            {
                this.props.todos.map(item => (<li>{item}</li>))
            }
           </React.Fragment>
        )
    }
}
class Todos extends React.Component{
    constructor() {
        super();
        this.state={todos:['a','b','c']};
    }
    render() {
        return (
            <ul>
                <List todos={this.state.todos}/>
            </ul>
        )
    }
}

ReactDOM.render(<Todos/>,document.querySelector('#root'));
複製程式碼

9. 插槽(Portals)

Portals 提供了一種很好的方法,將子節點渲染到父元件 DOM 層次結構之外的 DOM 節點。

ReactDOM.createPortal(child, container)
複製程式碼
  • 第一個引數(child)是任何可渲染的 React 子元素,例如一個元素,字串或 片段(fragment)
  • 第二個引數(container)則是一個 DOM 元素
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import './modal.css';

class Modal extends Component{
    constructor() {
        super();
        this.modal=document.querySelector('#modal-root');
    }
    render() {
        return ReactDOM.createPortal(this.props.children,this.modal);
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={show:false};
    }
    handleClick=() => {
        this.setState({show:!this.state.show});
    }
    render() {
        return (
            <div>
                <button onClick={this.handleClick}>顯示模態視窗</button>
                {
                    this.state.show&&<Modal>
                    <div id="modal" className="modal">
                        <div className="modal-content" id="modal-content">
                                內容
                                <button onClick={this.handleClick}>關閉</button>
                        </div>
                    </div>
                </Modal>
                }
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
複製程式碼
.modal{
    position: fixed;
    left:0;
    top:0;
    right:0;
    bottom:0;
    background: rgba(0,0,0,.5);
    display: block;
}

@keyframes zoom{
    from{transform:scale(0);}
    to{transform:scale(1);}
}

.modal .modal-content{
    width:50%;
    height:50%;
    background: white;
    border-radius: 10px;
    margin:100px auto;
    display:flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    animation: zoom .6s;
}
複製程式碼

10. 錯誤邊界(Error Boundaries)

部分 UI 中的 JavaScript 錯誤不應該破壞整個應用程式。 為了解決 React 使用者的這個問題,React 16引入了一個 “錯誤邊界(Error Boundaries)” 的新概念。

import React from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends React.Component{
    constructor(props) {
        super(props);
        this.state={hasError:false};
    }
    componentDidCatch(err,info) {
        this.setState({hasError: true});
    }
    render() {
        if (this.state.hasError) {
            return <h1>Something Went Wrong</h1>
        }
        return this.props.children;
    }
}

class Page extends React.Component{
    render() {
        return (
            <ErrorBoundary>
                <Clock/>
            </ErrorBoundary>
        )
    }
}
class Clock extends React.Component{
    render() {
        return (
            <div>hello{null.toString()}</div>
        )
    }
}

ReactDOM.render(<Page/>,document.querySelector('#root'));
複製程式碼

11. 高階元件(Higher-Order Components)

const NewComponent = higherOrderComponent(OldComponent)
複製程式碼
import React,{Component} from 'react';
import ReactDOM from 'react-dom';

export default (WrappedComponent,name) => {
    class HighOrderComponent extends Component{
        constructor() {
            super();
            this.state={data:null};
        }

        componentWillMount() {
            let data=localStorage.getItem(name);
            this.setState({data});
        }

        render() {
            return <WrappedComponent data={this.state.data}/>
        }
    }
    return HighOrderComponent;
}
複製程式碼

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import wrapLocalData from './wrapLocalData';
let UserName=(props) => {
    return <input type="text" defaultValue={props.data}/>
}
let Password=(props) => {
    return <input type="text" defaultValue={props.data}/>
}
let NewUserName=wrapLocalData(UserName,'username');
let NewPassword=wrapLocalData(Password,'password');
class Form extends Component{
    render() {
        return (
            <form>
                <NewUserName />
                <NewPassword/>
            </form>
        )
    }
}
ReactDOM.render(<Form/>,document.querySelector('#root'));
複製程式碼

import React,{Component} from 'react';
import ReactDOM from 'react-dom';

export default (WrappedComponent,name) => {
    class HighOrderComponent extends Component{
        constructor() {
            super();
            this.state={data:null};
        }

        componentWillMount() {
            fetch('/data.json',{
                method: 'GET'
            }).then(response => response.json()).then(data => {
                console.log(data[name]);
                this.setState({data:data[name]});
            })

        }

        render() {
            return <WrappedComponent data={this.state.data}/>
        }
    }
    return HighOrderComponent;
}

複製程式碼
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import wrapLocalData from './wrapLocalData';
import wrapAjax from './wrapAjax';
let UserName=(props) => {
    console.log('UserName ',props);
    return <input type="text" value={props.data} />
}
let Password=(props) => {
    return <input type="text" value={props.data}/>
}
let NewUserName=wrapAjax(UserName,'username');
let NewPassword=wrapAjax(Password,'password');
class Form extends Component{
    render() {
        return (
            <form>
                <NewUserName />
                <NewPassword/>
            </form>
        )
    }
}
ReactDOM.render(<Form/>,document.querySelector('#root'));
複製程式碼
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import wrapLocalData from './wrapLocalData';
import wrapAjax from './wrapAjax';
let UserName=(props) => {
    console.log('UserName ',props);
    return <input type="text" value={props.data} />
}
let Password=(props) => {
    return <input type="text" value={props.data}/>
}
UserName=wrapAjax(UserName);
UserName=wrapLocalData(UserName,'username');
class Form extends Component{
    render() {
        return (
            <form>
                <UserName />
            </form>
        )
    }
}
ReactDOM.render(<Form/>,document.querySelector('#root'));
複製程式碼

相關文章