React 開發必須知道的 34 個技巧【近1W字】

react 圖片.jpg

前言

React 是前端三大框架之一,在面試和開發中也是一項技能;
本文從實際開發中總結了 React 開發的一些技巧技巧,適合 React 初學或者有必定項目經驗的同窗;
萬字長文,建議收藏。
序列文章:Vue 開發必須知道的 36 個技巧【近1W字】css

源碼地址

請戳,歡迎 star html

效果圖
react 技巧.png前端

1 組件通信

1.1 props

子組件vue

import React from "react";
import PropTypes from "prop-types";
import { Button } from "antd";

export default class EightteenChildOne extends React.Component {
  static propTypes = { //propTypes校驗傳入類型,詳情在技巧11
    name: PropTypes.string
  };

  click = () => {
    // 經過觸發方法子傳父
    this.props.eightteenChildOneToFather("這是 props 改變父元素的值");
  };

  render() {
    return (
      <div>
        <div>這是經過 props 傳入的值{this.props.name}</div>
        <Button type="primary" onClick={this.click}>
          點擊改變父元素值
        </Button>
      </div>
    );
  }
}

父組件react

<EightteenChildOne name={'props 傳入的 name 值'} eightteenChildOneToFather={(mode)=>this.eightteenChildOneToFather(mode)}></EightteenChildOne>

props 傳多個值時:
傳統寫法webpack

const {dataOne,dataTwo,dataThree} = this.state
<Com dataOne={dataOne} dataTwo={dataTwo} dataThree={dataThree}>

升級寫法git

<Com {...{dataOne,dataTwo,dataThree}}>

1.2 props 升級版

原理:子組件裏面利用 props 獲取父組件方法直接調用,從而改變父組件的值
注意: 此方法和 props 大同小異,都是 props 的應用,因此在源碼中沒有舉例 es6

調用父組件方法改變該值github

// 父組件
state = {
  count: {}
}
changeParentState = obj => {
    this.setState(obj);
}
// 子組件
onClick = () => {
    this.props.changeParentState({ count: 2 });
}

1.3 Provider,Consumer和Context

1.Context在 16.x 以前是定義一個全局的對象,相似 vue 的 eventBus,若是組件要使用到該值直接經過this.context獲取web

//根組件
class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple",text: "item text"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
  text: React.PropTypes.string
};

//中間組件
class Message extends React.Component {
  render() {
    return (
      <div>
        <MessageItem />
        <Button>Delete</Button>
      </div>
    );
  }
}

//孫組件(接收組件)
class MessageItem extends React.Component {
  render() {
    return (
      <div>
        {this.context.text}
      </div>
    );
  }
}

MessageItem.contextTypes = {
  text: React.PropTypes.string //React.PropTypes在 15.5 版本被廢棄,看項目實際的 React 版本
};

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

2.16.x 以後的Context使用了Provider和Customer模式,在頂層的Provider中傳入value,在子孫級的Consumer中獲取該值,而且可以傳遞函數,用來修改context
聲明一個全局的 context 定義,context.js

import React from 'react'
let { Consumer, Provider } = React.createContext();//建立 context 並暴露Consumer和Provider模式
export {
    Consumer,
    Provider
}

父組件導入

// 導入 Provider
import {Provider} from "../../utils/context"

<Provider value={name}>
  <div style={{border:'1px solid red',width:'30%',margin:'50px auto',textAlign:'center'}}>
    <p>父組件定義的值:{name}</p>
    <EightteenChildTwo></EightteenChildTwo>
  </div>
</Provider>

子組件

// 導入Consumer
import { Consumer } from "../../utils/context"
function Son(props) {
  return (
    //Consumer容器,能夠拿到上文傳遞下來的name屬性,並能夠展現對應的值
    <Consumer>
      {name => (
        <div
          style={{
            border: "1px solid blue",
            width: "60%",
            margin: "20px auto",
            textAlign: "center"
          }}
        >
        // 在 Consumer 中能夠直接經過 name 獲取父組件的值
          <p>子組件。獲取父組件的值:{name}</p>
        </div>
      )}
    </Consumer>
  );
}
export default Son;

1.4 EventEmitter

EventEmiter 傳送門
使用 events 插件定義一個全局的事件機制

1.5 路由傳參

1.params

<Route path='/path/:name' component={Path}/>
<link to="/path/2">xxx</Link>
this.props.history.push({pathname:"/path/" + name});
讀取參數用:this.props.match.params.name

2.query

<Route path='/query' component={Query}/>
<Link to={{ path : '/query' , query : { name : 'sunny' }}}>
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
讀取參數用: this.props.location.query.name

3.state

<Route path='/sort ' component={Sort}/>
<Link to={{ path : '/sort ' , state : { name : 'sunny' }}}> 
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
讀取參數用: this.props.location.query.state

4.search

<Route path='/web/search ' component={Search}/>
<link to="web/search?id=12121212">xxx</Link>
this.props.history.push({pathname:`/web/search?id ${row.id}`});
讀取參數用: this.props.location.search

5.優缺點

1.params和 search 只能傳字符串,刷新頁面參數不會丟
2.query和 state 能夠傳對象,可是刷新頁面參數會丟失

1.6 onRef

原理:onRef 通信原理就是經過 props 的事件機制將組件的 this(組件實例)當作參數傳到父組件,父組件就能夠操做子組件的 state 和方法

EightteenChildFour.jsx

export default class EightteenChildFour extends React.Component {
  state={
      name:'這是組件EightteenChildFour的name 值'
  }

  componentDidMount(){
    this.props.onRef(this)
    console.log(this) // ->將EightteenChildFour傳遞給父組件this.props.onRef()方法
  }

  click = () => {
    this.setState({name:'這是組件click 方法改變EightteenChildFour改變的name 值'})
  };

  render() {
    return (
      <div>
        <div>{this.state.name}</div>
        <Button type="primary" onClick={this.click}>
          點擊改變組件EightteenChildFour的name 值
        </Button>
      </div>
    );
  }
}

eighteen.jsx

<EightteenChildFour onRef={this.eightteenChildFourRef}></EightteenChildFour>

eightteenChildFourRef = (ref)=>{
  console.log('eightteenChildFour的Ref值爲')
  // 獲取的 ref 裏面包括整個組件實例
  console.log(ref)
  // 調用子組件方法
  ref.click()
}

1.7 ref

原理:就是經過 React 的 ref 屬性獲取到整個子組件實例,再進行操做

EightteenChildFive.jsx

// 經常使用的組件定義方法
export default class EightteenChildFive extends React.Component {
  state={
      name:'這是組件EightteenChildFive的name 值'
  }

  click = () => {
    this.setState({name:'這是組件click 方法改變EightteenChildFive改變的name 值'})
  };

  render() {
    return (
      <div>
        <div>{this.state.name}</div>
        <Button type="primary" onClick={this.click}>
          點擊改變組件EightteenChildFive的name 值
        </Button>
      </div>
    );
  }
}

eighteen.jsx

// 鉤子獲取實例
componentDidMount(){
    console.log('eightteenChildFive的Ref值爲')
      // 獲取的 ref 裏面包括整個組件實例,一樣能夠拿到子組件的實例
    console.log(this.refs["eightteenChildFiveRef"])
  }

// 組件定義 ref 屬性
<EightteenChildFive ref="eightteenChildFiveRef"></EightteenChildFive>

1.8 redux

redux 是一個獨立的事件通信插件,這裏就不作過多的敘述

redux 請戳

1.9 MobX

mbox 也是一個獨立的事件通信插件,這裏就不作過多的敘述

mobx 請戳

1.10 flux

mobox 也是一個獨立的事件通信插件,這裏就不作過多的敘述

flux 請戳

1.11 hooks

1.hooks 是利用 userReducer 和 context 實現通信,下面模擬實現一個簡單的 redux
2.核心文件分爲 action,reducer,types
action.js

import * as Types from './types';

export const onChangeCount = count => ({
    type: Types.EXAMPLE_TEST,
    count: count + 1
})

reducer.js

import * as Types from "./types";
export const defaultState = {
  count: 0
};
export default (state, action) => {
  switch (action.type) {
    case Types.EXAMPLE_TEST:
      return {
        ...state,
        count: action.count
      };
    default: {
      return state;
    }
  }
};

types.js

export const EXAMPLE_TEST = 'EXAMPLE_TEST';

eightteen.jsx

export const ExampleContext = React.createContext(null);//建立createContext上下文

// 定義組件
function ReducerCom() {
  const [exampleState, exampleDispatch] = useReducer(example, defaultState);

  return (
    <ExampleContext.Provider
      value={{ exampleState, dispatch: exampleDispatch }}
    >
      <EightteenChildThree></EightteenChildThree>
    </ExampleContext.Provider>
  );
}

EightteenChildThree.jsx // 組件

import React, {  useEffect, useContext } from 'react';
import {Button} from 'antd'

import {onChangeCount} from '../../pages/TwoTen/store/action';
import { ExampleContext } from '../../pages/TwoTen/eighteen';

const Example = () => {

    const exampleContext = useContext(ExampleContext);

    useEffect(() => { // 監聽變化
        console.log('變化執行啦')
    }, [exampleContext.exampleState.count]);

    return (
        <div>
            <p>值爲{exampleContext.exampleState.count}</p>
            <Button onClick={() => exampleContext.dispatch(onChangeCount(exampleContext.exampleState.count))}>點擊加 1</Button>
        </div>
    )
}

export default Example;

3.hooks其實就是對原有React 的 API 進行了封裝,暴露比較方便使用的鉤子;

4.鉤子有:

鉤子名 做用
useState 初始化和設置狀態
useEffect componentDidMount,componentDidUpdate和componentWillUnmount和結合體,因此能夠監聽useState定義值的變化
useContext 定義一個全局的對象,相似 context
useReducer 能夠加強函數提供相似 Redux 的功能
useCallback 記憶做用,共有兩個參數,第一個參數爲一個匿名函數,就是咱們想要建立的函數體。第二參數爲一個數組,裏面的每一項是用來判斷是否須要從新建立函數體的變量,若是傳入的變量值保持不變,返回記憶結果。若是任何一項改變,則返回新的結果
useMemo 做用和傳入參數與 useCallback 一致,useCallback返回函數,useDemo 返回值
useRef 獲取 ref 屬性對應的 dom
useImperativeMethods 自定義使用ref時公開給父組件的實例值
useMutationEffect 做用與useEffect相同,但在更新兄弟組件以前,它在React執行其DOM改變的同一階段同步觸發
useLayoutEffect 做用與useEffect相同,但在全部DOM改變後同步觸發

5.useImperativeMethods

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeMethods(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

更多hooks 介紹請戳

1.12 對比

方法 優勢 缺點
props 不須要引入外部插件 兄弟組件通信須要創建共同父級組件,麻煩
props 升級版 不須要引入外部插件,子傳父,不須要在父組件用方法接收 同 props
Provider,Consumer和Context 不須要引入外部插件,跨多級組件或者兄弟組件通信利器 狀態數據狀態追蹤麻煩
EventEmitter 可支持兄弟,父子組件通信 要引入外部插件
路由傳參 可支持兄弟組件傳值,頁面簡單數據傳遞很是方便 父子組件通信無能爲力
onRef 能夠在獲取整個子組件實例,使用簡單 兄弟組件通信麻煩,官方不建議使用
ref 同 onRef 同 onRef
redux 創建了全局的狀態管理器,兄弟父子通信均可解決 引入了外部插件
mobx 創建了全局的狀態管理器,兄弟父子通信均可解決 引入了外部插件
flux 創建了全局的狀態管理器,兄弟父子通信均可解決 引入了外部插件
hooks 16.x 新的屬性,可支持兄弟,父子組件通信 須要結合 context 一塊兒使用

redux , mobx和flux對比

方法 介紹
redux 1.核心模塊:Action,Reducer,Store;2. Store 和更改邏輯是分開的;3. 只有一個 Store;4. 帶有分層 reducer 的單一 Store;5. 沒有調度器的概念;6. 容器組件是有聯繫的;7. 狀態是不可改變的;8.更多的是遵循函數式編程思想
mobx 1.核心模塊:Action,Reducer,Derivation;2.有多個 store;3.設計更多偏向於面向對象編程和響應式編程,一般將狀態包裝成可觀察對象,一旦狀態對象變動,就能自動得到更新
flux 1.核心模塊:Store,ReduceStore,Container;2.有多個 store;

2.require.context()

這個是 webpack 的 api,這個在 vue 技巧中有介紹,由於 Vue 和 React 工程都是基於 webpack打包,因此在 react 也可使用

const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
  const name = path.basename(key, '.vue')
  modules[name] = files(key).default || files(key)
})

3.Decorator

定義:decorator是ES7的一個新特性,能夠修改class的屬性

import React from 'react'
import Test from '../../utils/decorators'

@Test
//只要Decorator後面是Class,默認就已經把Class當成參數隱形傳進Decorator了。
class TwentyNine extends React.Component{
    componentDidMount(){
        console.log(this,'decorator.js') // 這裏的this是類的一個實例
        console.log(this.testable)
    }
    render(){
        return (
            <div>這是技巧23</div>
        )
    }
}

export default TwentyNine

decorators.js

function testable(target) {
  console.log(target)
  target.isTestable = true;
  target.prototype.getDate = ()=>{
    console.log( new Date() )
  }
}

export default testable

不少中間件,像 redux 裏面就封裝了Decorator的使用

4.使用 if...else

場景:有些時候須要根據不一樣狀態值頁面顯示不一樣內容

import React from "react";

export default class Four extends React.Component {
  state = {
    count: 1
  };
  render() {
    let info
    if(this.state.count===0){
      info=(
        <span>這是數量爲 0 顯示</span>
      )
    } else if(this.state.count===1){
      info=(
        <span>這是數量爲 1 顯示</span>
      )
    }
    return (
      <div>
        {info}
      </div>
    );
  }
}

5.state 值改變的四種方式

方式 1

let {count} = this.state
this.setState({count:2})

方式 2:callBack

this.setState(({count})=>({count:count+2}))

方式 3:接收 state 和 props 參數

this.setState((state, props) => {
    return { count: state.count + props.step };
});

方式 4:hooks

const [count, setCount] = useState(0)
// 設置值
setCount(count+2)

6.監聽states 變化

1.16.x 以前使用componentWillReveiveProps

componentWillReceiveProps (nextProps){
  if(this.props.visible !== nextProps.visible){
      //props 值改變作的事
  }
}

注意:有些時候componentWillReceiveProps在 props 值未變化也會觸發,由於在生命週期的第一次render後不會被調用,可是會在以後的每次render中被調用 = 當父組件再次傳送props

2.16.x 以後使用getDerivedStateFromProps,16.x 之後componentWillReveiveProps也未移除

export default class Six extends React.Component {
  state = {
    countOne:1,
    changeFlag:''
  };
  clickOne(){
    let {countOne} = this.state
    this.setState({countOne:countOne+1})
  };
  static getDerivedStateFromProps (nextProps){
    console.log('變化執行')
    return{
      changeFlag:'state 值變化執行'
    }
  }
  render() {
    const {countOne,changeFlag} = this.state
    return (
      <div>
        <div>
         <Button type="primary" onClick={this.clickOne.bind(this)}>點擊加 1</Button><span>countOne 值爲{countOne}</span>
        <div>{changeFlag}</div>
        </div>
      </div>
    );
  }
}

7.組件定義方法

方式 1:ES5 的Function 定義

function FunCom(props){
    return <div>這是Function 定義的組件</div>
}
ReactDOM.render(<FunCom name="Sebastian" />, mountNode)

// 在 hooks 未出來以前,這個是定義無狀態組件的方法,如今有了 hooks 也能夠處理狀態

方式 2: ES5的 createClass 定義

const CreateClassCom = React.createClass({
  render: function() {
  return <div>這是React.createClass定義的組件</div>
  }
});

方式 3:ES6 的 extends

class Com extends React.Component {
  render(){
    return(<div>這是React.Component定義的組件</div>)
  }
}

調用

export default class Seven extends React.Component {
  render() {
    return (
      <div>
        <FunCom></FunCom>
        <Com></Com>
      </div>
    );
  }
}

區別: ES5的 createClass是利用function模擬class的寫法作出來的es6;

經過es6新增class的屬性建立的組件此組件建立簡單.

8.經過 ref 屬性獲取 component

方式 1:也是最先的用法,經過 this.refs[屬性名獲取]
也能夠做用到組件上,從而拿到組件實例

class RefOne extends React.Component{
  componentDidMount() {
    this.refs['box'].innerHTML='這是 div 盒子,經過 ref 獲取'
  }
  render(){
    return(
      <div ref="box"></div>
    )
  }
}

方式 2:回調函數,在dom節點或組件上掛載函數,函數的入參是dom節點或組件實例,達到的效果與字符串形式是同樣的,都是獲取其引用

class RefTwo extends React.Component{
  componentDidMount() {
    this.input.value='這是輸入框默認值';
    this.input.focus();
  }
  render(){
    return(
      <input ref={comp => { this.input = comp; }}/>
    )
  }
}

方式 3:React.createRef()
React 16.3版本後,使用此方法來建立ref。將其賦值給一個變量,經過ref掛載在dom節點或組件上,該ref的current屬性,將能拿到dom節點或組件的實例

class RefThree extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myRef.current);
  }
  render(){
    return <input ref={this.myRef}/>
  }
}

方式 4:React.forwardRef
React 16.3版本後提供的,能夠用來建立子組件,以傳遞ref

class RefFour extends React.Component{
  constructor(props){
    super(props);
    this.myFourRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myFourRef.current);
  }
  render(){
    return <Child ref={this.myFourRef}/>
  }
}

子組件經過React.forwardRef來建立,能夠將ref傳遞到內部的節點或組件,進而實現跨層級的引用。forwardRef在高階組件中能夠獲取到原始組件的實例.這個功能在技巧 18 會着重講

9.static 使用

場景:聲明靜態方法的關鍵字,靜態方法是指即便沒有組件實例也能夠直接調用

export default class Nine extends React.Component {
  static update(data) {
    console.log('靜態方法調用執行啦')
  }
  render() {
    return (
      <div>
        這是 static 關鍵字技能
      </div>
    );
  }
}

Nine.update('2')

注意:
1.ES6的class,咱們定義一個組件的時候一般是定義了一個類,而static則是建立了一個屬於這個類的屬性或者方法
2.組件則是這個類的一個實例,component的props和state是屬於這個實例的,因此實例還未建立
3.因此static並非react定義的,而加上static關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,因此也是沒法訪問到 this
4.getDerivedStateFromProps也是經過靜態方法監聽值,詳情請見技巧 6

10.constructor和super

回顧:
1.談這兩個屬性以前,先回顧一下ES6 函數定義方法
2.每個使用class方式定義的類默認都有一個constructor函數, 這個函數是構造函數的主函數, 該函數體內部的this指向生成的實例
3.super關鍵字用於訪問和調用一個對象的父對象上的函數

export default class Ten extends React.Component {
  constructor() { // class 的主函數
    super() // React.Component.prototype.constructor.call(this),其實就是拿到父類的屬性和方法
    this.state = {
      arr:[]
    }
  }  
  render() {
    return (
      <div>
        這是技巧 10
      </div>
    );
  }
}

11.PropTypes

場景:檢測傳入子組件的數據類型
類型檢查PropTypes自React v15.5起已棄用,請使用prop-types
方式 1:舊的寫法

class PropTypeOne extends React.Component {
  render() {
    return (
      <div>
        <div>{this.props.email}</div>
        <div>{this.props.name}</div>
      </div>
    );
  }
}

PropTypeOne.propTypes = {
  name: PropTypes.string, //值可爲array,bool,func,number,object,symbol
  email: function(props, propName, componentName) { //自定義校驗
    if (
      !/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(
        props[propName]
      )
    ) {
      return new Error(
        "組件" + componentName + "裏的屬性" + propName + "不符合郵箱的格式"
      );
    }
  },
};

方法 2:利用 ES7 的靜態屬性關鍵字 static

class PropTypeTwo extends React.Component {
  static propTypes = {
      name:PropTypes.string
  };
  render() {
    return (
      <div>
        <div>{this.props.name}</div>
      </div>
    );
  }
}

12.使用類字段聲明語法

場景:能夠在不使用構造函數的狀況下初始化本地狀態,並經過使用箭頭函數聲明類方法,而無需額外對它們進行綁定

class Counter extends Component {
  state = { value: 0 };

  handleIncrement = () => {
    this.setState(prevState => ({
      value: prevState.value + 1
    }));
  };

  handleDecrement = () => {
    this.setState(prevState => ({
      value: prevState.value - 1
    }));
  };

  render() {
    return (
      <div>
        {this.state.value}

        <button onClick={this.handleIncrement}>+</button>
        <button onClick={this.handleDecrement}>-</button>
      </div>
    )
  }
}

13.異步組件

1.場景:路由切換,若是同步加載多個頁面路由會致使緩慢

2.核心 API:
loader:須要加載的組件
loading:未加載出來的頁面展現組件
delay:延遲加載時間
timeout:超時時間

3.使用方法:
安裝 react-loadable ,babel插件安裝 syntax-dynamic-import. react-loadable是經過webpack的異步import實現的

const Loading = () => {
  return <div>loading</div>;
};

const LoadableComponent = Loadable({
  loader: () => import("../../components/TwoTen/thirteen"),
  loading: Loading
});

export default class Thirteen extends React.Component {
  render() {
    return <LoadableComponent></LoadableComponent>;
  }
}

4.Loadable.Map()
並行加載多個資源的高階組件

14.動態組件

場景:作一個 tab 切換時就會涉及到組件動態加載
實質上是利用三元表達式判斷組件是否顯示

class FourteenChildOne extends React.Component {
    render() {
        return <div>這是動態組件 1</div>;
    }
}

class FourteenChildTwo extends React.Component {
    render() {
        return <div>這是動態組件 2</div>;
    }
}

export default class Fourteen extends React.Component {
  state={
      oneShowFlag:true
  }
  tab=()=>{
      this.setState({oneShowFlag:!this.state.oneShowFlag})
  }
  render() {
    const {oneShowFlag} = this.state
    return (<div>
        <Button type="primary" onClick={this.tab}>顯示組件{oneShowFlag?2:1}</Button>
        {oneShowFlag?<FourteenChildOne></FourteenChildOne>:<FourteenChildTwo></FourteenChildTwo>}
    </div>);
  }
}

15.遞歸組件

場景:tree組件
利用React.Fragment或者 div 包裹循環

class Item extends React.Component {
  render() {
    const list = this.props.children || [];
    return (
      <div className="item">
        {list.map((item, index) => {
          return (
            <React.Fragment key={index}>
              <h3>{item.name}</h3>
              {// 當該節點還有children時,則遞歸調用自己
              item.children && item.children.length ? (
                <Item>{item.children}</Item>
              ) : null}
            </React.Fragment>
          );
        })}
      </div>
    );
  }
}

16.受控組件和不受控組件

受控組件:組件擁有本身的狀態

class Controll extends React.Component {
  constructor() {
    super();
    this.state = { value: "這是受控組件默認值" };
  }
  render() {
    return <div>{this.state.value}</div>;
  }
}

不受控組件:組件無本身的狀態,在父組件經過 ref 來控制或者經過 props 傳值

class NoControll extends React.Component {
  render() {
    return <div>{this.props.value}</div>;
  }
}

導入代碼:

export default class Sixteen extends React.Component {
  componentDidMount() {
    console.log("ref 獲取的不受控組件值爲", this.refs["noControll"]);
  }
  render() {
    return (
      <div>
        <Controll></Controll>
        <NoControll
          value={"這是不受控組件傳入值"}
          ref="noControll"
        ></NoControll>
      </div>
    );
  }
}

17.高階組件

17.1 定義

就是相似高階函數的定義,將組件做爲參數或者返回一個組件的組件

17.2 實現方法

1.屬性代理

import React,{Component} from 'react';

const Seventeen = WraooedComponent =>
  class extends React.Component {
    render() {
      const props = {
        ...this.props,
        name: "這是高階組件"
      };
      return <WrappedComponent {...props} />;
    }
  };

class WrappedComponent extends React.Component {
  state={
     baseName:'這是基礎組件' 
  }
  render() {
    const {baseName} = this.state
    const {name} = this.props
    return <div>
        <div>基礎組件值爲{baseName}</div>
        <div>經過高階組件屬性代理的獲得的值爲{name}</div>
    </div>
  }
}

export default Seventeen(WrappedComponent)

2.反向繼承
原理就是利用 super 改變改組件的 this 方向,繼而就能夠在該組件處理容器組件的一些值

const Seventeen = (WrappedComponent)=>{
    return class extends WrappedComponent {
        componentDidMount() {
            this.setState({baseName:'這是經過反向繼承修改後的基礎組件名稱'})
        }
        render(){
            return super.render();
        }
    }
}

class WrappedComponent extends React.Component {
  state={
     baseName:'這是基礎組件' 
  }
  render() {
    const {baseName} = this.state
    return <div>
        <div>基礎組件值爲{baseName}</div>
    </div>
  }
}

export default Seventeen(WrappedComponent);

18.元素是否顯示

通常用三元表達式

flag?<div>顯示內容</div>:''

19.Dialog 組件建立

Dialog 應該是用的比較多的組件,下面有三種不一樣的建立方法
方式 1:經過 state 控制組件是否顯示

class NineteenChildOne extends React.Component {
  render() {
    const Dialog = () => <div>這是彈層1</div>;

    return this.props.dialogOneFlag && <Dialog />;
  }
}

方式 2:經過ReactDom.render建立彈層-掛載根節點外層
經過原生的createElement,appendChild, removeChild和react 的ReactDOM.render,ReactDOM.unmountComponentAtNode來控制元素的顯示和隱藏

NineteenChild.jsx

import ReactDOM from "react-dom";

class Dialog {
  constructor(name) {
    this.div = document.createElement("div");
    this.div.style.width = "200px";
    this.div.style.height = "200px";
    this.div.style.backgroundColor = "green";
    this.div.style.position = "absolute";
    this.div.style.top = "200px";
    this.div.style.left = "400px";
    this.div.id = "dialog-box";
  }
  show(children) {
    // 銷燬
    const dom = document.querySelector("#dialog-box");
    if(!dom){ //兼容屢次點擊
      // 顯示
      document.body.appendChild(this.div);
      ReactDOM.render(children, this.div);
    }
  }
  destroy() {
    // 銷燬
    const dom = document.querySelector("#dialog-box");
    if(dom){//兼容屢次點擊
      ReactDOM.unmountComponentAtNode(this.div);
      dom.parentNode.removeChild(dom);
    }
  }
}
export default {
  show: function(children) {
    new Dialog().show(children);
  },
  hide: function() {
    new Dialog().destroy();
  }
};

nineteen.jsx

twoSubmit=()=>{
    Dialog.show('這是彈層2')
  }

  twoCancel=()=>{
    Dialog.hide()
  }

20.React.memo

做用:當類組件的輸入屬性相同時,可使用 pureComponent 或 shouldComponentUpdate 來避免組件的渲染。如今,你能夠經過把函數組件包裝在 React.memo 中來實現相同的功能

import React from "react";

function areEqual(prevProps, nextProps) {
  /*
  若是把 nextProps 傳入 render 方法的返回結果與
  將 prevProps 傳入 render 方法的返回結果一致則返回 true,
  不然返回 false
  */
  if (prevProps.val === nextProps.val) {
    return true;
  } else {
    return false;
  }
}

// React.memo()兩個參數,第一個是純函數,第二個是比較函數
export default React.memo(function twentyChild(props) {
  console.log("MemoSon rendered : " + Date.now());
  return <div>{props.val}</div>;
}, areEqual);

21.React.PureComponent

做用:
1.React.PureComponent 和 React.Component相似,都是定義一個組件類。
2.不一樣是React.Component沒有實現shouldComponentUpdate(),而 React.PureComponent經過props和state的淺比較實現了。
3.React.PureComponent是做用在類中,而React.memo是做用在函數中。
4.若是組件的props和state相同時,render的內容也一致,那麼就可使用React.PureComponent了,這樣能夠提升組件的性能

class TwentyOneChild extends React.PureComponent{  //組件直接繼承React.PureComponent
  render() {
    return <div>{this.props.name}</div>
  }
}

export default class TwentyOne extends React.Component{
    render(){
        return (
            <div>
              <TwentyOneChild name={'這是React.PureComponent的使用方法'}></TwentyOneChild>
            </div>
        )
    }
}

22.React.Component

做用:是基於ES6 class的React組件,React容許定義一個class或者function做爲組件,那麼定義一個組件類,就須要繼承React.Component

export default class TwentyTwo extends React.Component{ //組件定義方法
    render(){
        return (
            <div>這是技巧22</div>
        )
    }
}

23.在 JSX 打印 falsy 值

定義:
1.falsy 值 (虛值) 是在 Boolean 上下文中認定爲 false 的值;
2.值有 0,"",'',``,null,undefined,NaN

export default class TwentyThree extends React.Component{
    state={myVariable:null}
    render(){
        return (
            <div>{String(this.state.myVariable)}</div>
        )
    }
}

虛值若是直接展現,會發生隱式轉換,爲 false,因此頁面不顯示

24.ReactDOM.createPortal

做用:組件的render函數返回的元素會被掛載在它的父級組件上,createPortal 提供了一種將子節點渲染到存在於父組件之外的 DOM 節點的優秀的方案

import React from "react";
import ReactDOM from "react-dom";
import {Button} from "antd"

const modalRoot = document.body;

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement("div");
    this.el.style.width = "200px";
    this.el.style.height = "200px";
    this.el.style.backgroundColor = "green";
    this.el.style.position = "absolute";
    this.el.style.top = "200px";
    this.el.style.left = "400px";
  }

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

function Child() {
  return (
    <div className="modal">
      這個是經過ReactDOM.createPortal建立的內容
    </div>
  );
}

export default class TwentyFour extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div>
          <Button onClick={this.handleClick}>點擊加1</Button>
        <p>點擊次數: {this.state.clicks}</p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

這樣元素就追加到指定的元素下面啦

25.在 React 使用innerHTML

場景:有些後臺返回是 html 格式字段,就須要用到 innerHTML 屬性

export default class TwentyFive extends React.Component {
  render() {
    return (
      <div dangerouslySetInnerHTML={{ __html: "<span>這是渲染的 HTML 內容</span>" }}></div>
    );
  }
}

爲何是危險的屬性,由於這個會致使 XSS 攻擊
_html是兩個

26.React.createElement

語法:
React.createElement(
type,
[props],
[...children]
)

源碼:

export default class TwentySix extends React.Component {
  render() {
    return (
      <div>
        {React.createElement(
          "div",
          { id: "one", className: "two" },
          React.createElement("span", { id: "spanOne" }, "這是第一個 span 標籤"),
          React.createElement("br"),
          React.createElement("span", { id: "spanTwo" }, "這是第二個 span 標籤")
        )}
      </div>
    );
  }
}

原理:實質上 JSX 的 dom 最後轉化爲 js 都是React.createElement

// jsx 語法
<div id='one' class='two'>
    <span id="spanOne">this is spanOne</span>
    <span id="spanTwo">this is spanTwo</span>
</div>

// 轉化爲 js
React.createElement(
  "div",
 { id: "one", class: "two" },
 React.createElement( "span", { id: "spanOne" }, "this is spanOne"), 
 React.createElement("span", { id: "spanTwo" }, "this is spanTwo")
);

27.React.cloneElement

語法:

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

做用:這個方法的做用是複製組件,給組件傳值或者添加屬性
核心代碼

React.Children.map(children, child => {
  return React.cloneElement(child, {
    count: _this.state.count
  });
});

28.React.Fragment

做用:React.Fragment可讓你聚合一個子元素列表,而且不在DOM中增長額外節點
核心代碼

render() {
    const { info } = this.state;
    return (
      <div>
        {info.map((item, index) => {
          return (
            <React.Fragment key={index}>
              <div>{item.name}</div>
              <div>{item.age}</div>
            </React.Fragment>
          );
        })}
      </div>
    );
  }

29.綁定事件

場景:交互就會涉及到事件點擊,而後點擊選中值傳參也是一個很常見場景

import React from "react";
import { Button } from 'antd'

export default class Three extends React.Component {
  state = {
    flag: true,
    flagOne: 1
  };
  click(data1,data2){
    console.log('data1 值爲',data1)
    console.log('data2 值爲',data2)
  }
  render() {
    return (
      <div>
        <Button type="primary" onClick={this.click.bind(this,'參數 1','參數 2')}>點擊事件</Button>
      </div>
    );
  }
}

30.給 DOM 設置和獲取自定義屬性

做用:有些要經過自定義屬性傳值

export default class Thirty extends React.Component {
  click = e => {
    console.log(e.target.getAttribute("data-row"));
  };

  render() {
    return (
      <div>
        <div data-row={"屬性1"} data-col={"屬性 2"} onClick={this.click}>
          點擊獲取屬性
        </div>
      </div>
    );
  }
}

31.循環元素

內部沒有封裝像 vue 裏面 v-for 的指令,而是經過 map 遍歷

使用方法在源碼 routes.js 有詳細使用

32.React-Router

32.1 V3和 V4的區別

1.V3或者說V早期版本是把router 和 layout components 分開;
2.V4是集中式 router,經過 Route 嵌套,實現 Layout 和 page 嵌套,Layout 和 page 組件 是做爲 router 的一部分
3.在V3 中的 routing 規則是 exclusive,意思就是最終只獲取一個 route
4.V4 中的 routes 默認是 inclusive 的,這就意味着多個 <Route>能夠同時匹配和呈現.若是隻想匹配一個路由,可使用Switch,在 <Switch> 中只有一個 <Route> 會被渲染,同時能夠再在每一個路由添加exact,作到精準匹配
Redirect,瀏覽器重定向,當多有都不匹配的時候,進行匹配

32.2 使用

import { HashRouter as Router, Switch  } from "react-router-dom";

class App extends React.Component{
    render(){
        const authPath = '/login' // 默認未登陸的時候返回的頁面,能夠自行設置
        let authed = this.props.state.authed || localStorage.getItem('authed') // 若是登錄以後能夠利用redux修改該值
        return (
            <Router>
                <Switch>
                    {renderRoutes(routes, authed, authPath)}
                </Switch>
            </Router>
        )
    }
}

V4是經過 Route 嵌套,實現 Layout 和 page 嵌套,Switch切換路由的做用

33.樣式引入方法

方式 1:import 導入

import './App.css';

方式 2:內聯方式

import React from 'react';

const Header = () => {

    const heading = '頭部組件'

    return(
        <div style={{backgroundColor:'orange'}}>
            <h1>{heading}</h1>
        </div>
    )
}

或者
import React from 'react';

const footerStyle = {
    width: '100%',
    backgroundColor: 'green',
    padding: '50px',
    font: '30px',
    color: 'white',
    fontWeight: 'bold'
}

export const Footer = () => {
    return(
        <div style={footerStyle}>
            底部組件
        </div>
    )
}

34.動態綁定 className

原理:經過三元表達式控制 className 值

render(){
  const flag=true
  return (
    <div className={flag?"active":"no-active"}>這是技巧 34</div>
  )
}

總結

這就是從實際項目開發總結的 React的 34 個技巧;
原創碼字不易,歡迎 star;
源碼地址,請戳,歡迎 star

相關文章
相關標籤/搜索