天天看點

【React】1011- 來自 Vue 程式員總結的 React 基礎

【React】1011- 來自 Vue 程式員總結的 React 基礎

一、生命周期

React 生命周期圖解[1]

我已經把這張圖印在腦子裡面了,沒事就自己畫畫,中間發散一些自己的思考。u1s1,不知道 react 的生命周期命名為什麼要怎麼長~~~, 小程式,vue 的都比較短。畢竟使用的頻率還是很高的,Hooks 除外。

【React】1011- 來自 Vue 程式員總結的 React 基礎

image.png

1、constructor

constructor 是類通用的構造函數,常用于初始化,算是生命周期的一環。React 後來的版本中類元件也可以不寫。

注意:在構造函數中使用時,​

​super​

​​ 關鍵字将單獨出現,并且​

​必須​

​​在使用 ​

​this​

​ 關鍵字之前使用。super 關鍵字也可以用來調用父對象上的函數。MDN 說明[2]

class JJTest extends React.Component {
  // constructor 寫法
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
    this.handleClick = this.handleClick.bind(this);
  }
  // 直接聲明
  state = {
    count: 0,
  };
}      

2、getDerivedStateFromProps

觸發時機:​

​state 變化、props 變化、forceUpdate​

​,如上圖。

這是一個靜态方法, 是一個群組件自身"不相關"的角色. 在這個靜态方法中, 除了兩個預設的位置參數 nextProps 和 currentState 以外, 你​

​無法通路​

​任何元件上的資料。

// 初始化/更新時調用
static getDerivedStateFromProps(nextProps, currentState) {
  console.log(nextProps, currentState, "getDerivedStateFromProps方法執行");
  // 傳回值是對currentState進行修改
  return {
    fatherText: nextProps.text,
  };
}      

3、render

render 函數傳回的 JSX 結構,用于描述具體的渲染内容, render 被調用時,它會檢查 this.props 和 this.state 的變化并傳回以下類型之一:

  • ​React 元素​

    ​​。通常通過 JSX 建立。例如,

    會被 React 渲染為 DOM 節點, 會被 React 渲染為自定義元件,無論是

    還是 均為 React 元素。

  • ​數組或 fragments​

    ​​。使得 render 方法可以傳回多個元素。欲了解更多詳細資訊,請參閱 fragments 文檔。
  • ​Portals​

    ​​。可以渲染子節點到不同的 DOM 子樹中。欲了解更多詳細資訊,請參閱有關 portals 的文檔
  • ​字元串或數值類型​

    ​​。它們在 DOM 中會被渲染為文本節點
  • ​布爾類型或 null​

    ​​。什麼都不渲染。(主要用于支援傳回 test && 的模式,其中 test 為布爾類型。)

注意:如果 shouldComponentUpdate() 傳回 false,則不會調用 render()。

Hooks 不需要寫 render 函數。要注意的一點是,即使 Hooks 不需要寫 render, 沒有用到 React.xxx,元件内還是要​

​import React from "react";​

​的(至于原因,後續深入 Hooks 學一下,大哥們也可以解釋下)。React 官方也說了,後續的版本會優化掉這一點。

4、componentDidMount

主要用于元件加載完成時做某些操作,比如發起網絡請求或者綁定事件。當做 vue 的 mounted 用就行了,這裡需要注意的是:

componentDidMount() 裡直接調用 setState()。它将觸發額外渲染,也就是兩次 render,不過問題不大,主要還是了解。

5、shouldComponentUpdate

該方法通過傳回 ​

​true​

​​ 或者 ​

​false​

​​ 來确定是否需要觸發新的渲染。因為渲染觸發最後一道關卡,是以也是​

​性能優化​

​​的必争之地。通過添加判斷條件來阻止不必要的渲染。注意:​

​首次渲染​

​​或使用 ​

​forceUpdate()​

​ 時不會調用該方法。

React 官方提供了一個通用的優化方案,也就是 ​

​PureComponent​

​​。PureComponent 的核心原理就是預設實作了 shouldComponentUpdate 函數,在這個函數中對 props 和 state 進行​

​淺比較​

​,用來判斷是否觸發更新。

當然 PureComponent 也是有​

​缺點​

​​的,使用的時候一定要注意:由于進行的是淺比較,可能由于深層的資料不一緻導緻而産生錯誤的否定判斷,進而導緻頁 面​

​得不到更新​

​​。不适合使用在含有​

​多層嵌套對象​

​的 state 和 prop 中。

shouldComponentUpdate(nextProps, nextState) {
  // 淺比較僅比較值與引用,并不會對 Object 中的每一項值進行比較
  if (shadowEqual(nextProps, this.props) || shadowEqual(nextState, this.state) ) {
    return true
  }
  return false
}      

6、getSnapshotBeforeUpdate

在 DOM ​

​更新前​

​被調用,傳回值将作為 componentDidUpdate 的第三個參數。

getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate方法執行");

    return "componentDidUpdated的第三個參數";
}      

7、componentDidUpdate

首次渲染不會執行此方法。可以使用 setState,會觸發重渲染,但一定要小心使用,避免死循環

componentDidUpdate(preProps, preState, valueFromSnapshot) {
    console.log("componentDidUpdate方法執行");

    console.log("從 getSnapshotBeforeUpdate 擷取到的值是", valueFromSnapshot);
  }      

8、componentWillUnmount

主要用于一些事件的解綁,資源清理等,比如取消定時器,取消訂閱事件

小結

生命周期一定要好好了解,一定要動手寫,看一下每種情況下,生命周期的執行結果。上述代碼中在React-TypeScript 倉庫[3]中都有,可以 clone 下來跑跑看,或者直接通路俊劫學習系統 LifeCycle[4]。還有些其他的生命周期,​

​componentDidCatch, UNSAFE_componentWillMount()​

​等等,簡單了解下就行。

二、JSX

1、循環清單

jsx 中一般用 map 來渲染清單循環類的,vue 中直接在 template 中寫 v-for 即可

{
  list.map((item, index) => {
    return <AppCard key={index} title={item.title} onClick={item.onClick} />;
  });
}      

2、樣式

(1)className

單獨寫一個 class 是可以的,動态拼接需要借助 classnames[5] 庫

import style from './style.css'

<div className={style.class1 style.class2}</div>      

(2)style

需要注意的:兩個括号(樣式被當做一個對象來解析),類似-連接配接的樣式屬性需要轉換成小駝峰寫法。

<div style={{ marginTop: 8 }}>樣式</div>      

(3)css 隔離

u1s1,css 隔離這塊還是 vue 的 scoped 好用

  • css-module

create-react-app 中内置了使用 CSS Modules 的配置,和 vue 的 scoped 原理相似,都是在生成的 class 後面加了 hash 值

// style.module.css
.text {
    color: blue
}
// app.tsx
import s from "./style.module.css";
class App extends Component {
  render() {
    return <div className={s.text}>css-module text</div>;
  }
}
// 編譯後
.text_3FI3s6uz {
    color: blue;
}      
  • styled-components

目前社群裡最受歡迎的一款 ​

​CSS in JS​

​ 方案,個人感覺有點别扭,不太喜歡

//引入styled-components
import styled from "styled-components";

//修改了div的樣式
const Title = styled.div`
  font-size: 30px;
  color: red;
`;
class App extends Component {
  render() {
    return (
      <>
        <Title>CSS in JS 方案</Title>
      </>
    );
  }
}      

3、一個 JSX

剛開始從 vue 轉過來會有些不适應(話說有多少人直接在 vue 裡面寫 JSX 的),之前用的都是 Vue Sfc 寫法,當然多寫寫就熟悉了。至于 React 采用 JSX 的優劣勢,評論區各抒己見哈。

代碼對應頁面預覽[6]

【React】1011- 來自 Vue 程式員總結的 React 基礎

image.png

render() {
    return (
      <>
        <Alert title="控制台展示父子元件生命周期的過程" />
        <div className="fatherContainer">
          <Button onClick={this.changeText} type="primary">
            修改父元件文本内容
          </Button>
          <Button onClick={this.hideChild} type="danger">
            {this.state.hideChild ? "顯示" : "隐藏"}子元件
          </Button>
          {this.state.hideChild ? null : (
            <LifeCycle text={this.state.text} count={1} />
          )}
        </div>
        <div>
          <BlockLoading loading={this.state.loading} iconSize={64} />
          <iframe
            src={this.state.lifeCycle}
            title="navigation"
            width="100%"
            height="600px"
            onLoad={this.onLoading}
            onError={this.onLoading}
          ></iframe>
        </div>
      </>
    );
  }      

三、基礎元件

元件這塊,個人感覺和 vue 差别還是比較大的,顆粒度更細緻,當然也增加了一定難度。這裡就簡單例舉一個TS版本的,​

​帶 Icon 的标題元件​

import cn from "classnames";
import React from "react";
import "./style/index.less";
import { Icon,IIconProps } from "zent";

interface IProps {
  title: string;
  iconType?: IIconProps['type'];
  isShowIcon?: boolean;
  iconClassName?: string;
  titleClassName?: string;
}

export const ContentTitle: React.FC<IProps> = (props) => {
  const { title, iconType = 'youzan', isShowIcon = false , iconClassName, titleClassName, ...popProps } = props;

  return (
    <div className={cn("content-title", titleClassName)}>
      {title}
      {isShowIcon && <Icon
        className={cn("content-title__icon", iconClassName)}
        {...popProps}
        type={iconType}
      />}
    </div>
  );
};

export default ContentTitle;
      

四、高階元件 HOC

1、含義

和 ​

​vue mixins​

​​ 相同,都是為了解決​

​代碼複用​

​​的問題,但 react 中已經廢棄 mixins,vue 中也不推薦使用。主要是會帶來​

​命名沖突,互相依賴,不友善維護​

​等一些缺點。

高階元件其實就是處理 react 元件的​

​函數​

​​,簡單了解就是和 ES6 中提供的 export/import 作用相似,不同點在于:高階元件會進行​

​加工後​

​​再導出你需要的東西。類似于方程式:​

​y = ax + b​

​, x 是入口(元件),會根據 a 和 b 進行計算,得到最終的 y(處理後的元件) 給到你用。

2、Demo

官網的實作 Demo: 高階元件[7]

一個簡單的高階元件(實作有兩種方式:屬性代理和反向繼承):

// 屬性代理: 元件屬性的一些修改
const JJHOC = (WrappedComponent) => {
  return class NewComponent extends React.Component {
    render() {
      const newProps = { type: "HOC" };
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  };
};
// 反向繼承: 在render() 方法中傳回 super.render() 方法
const JJHOC = (WrappedComponent) => {
  return class NewComponent extends WrappedComponent {
    render() {
      return super.render();
    }
  };
};      

3、常用 HOC

  • react-router withRouter: 可擷取 history,一些路由資訊
  • redux connect 連接配接 React 元件與 Redux store,給元件挂載 dispatch 方法。

五、元件通信

1、props

和 vue 不同的是,react props 傳值可以​

​直接寫​

​​,不需要聲明。在 props 上挂載 ​

​function​

​​,就相當于是 vue 的​

​$emit​

​​。同樣需要注意的是子元件不可以​

​修改 props​

​ 的值

import React from "react";

function Child(props) {
  const sendMsg = (msg) => {
    props.onClick("子元件的消息");
  };
  return (
    <div>
      <div>子元件标題:{props.title}</div>
      <button onClick={() => sendMsg("子元件消息")}> 子傳父 </button>
    </div>
  );
}

function Father() {
  const onClick = (msg) => {
    console.log(`父元件接收:${msg}`);
  };
  return (
    <div>
      <Child title="元件props傳值測試" onClick={onClick}></Child>
    </div>
  );
}

export default Father;      

2、context

React Context 官網說明[8],跨元件傳值。建立了一個上下文,同 context 内的元件都可以 通過 Provider 配合 value 使用資料

import * as React from "react";
import { Button } from "zent";
// Context 可以讓我們無須明确地傳遍每一個元件,就能将值深入傳遞進元件樹。
// 為目前的 theme 建立一個 context(“primary”為預設值)。
const ThemeContext = React.createContext("primary");
export default class App extends React.Component {
  render() {
    // 使用一個 Provider 來将目前的 theme 傳遞給以下的元件樹。
    // 無論多深,任何元件都能讀取這個值。
    // 在這個例子中,我們将 danger 作為目前的值傳遞下去。
    return (
      <ThemeContext.Provider value="danger">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的元件再也不必指明往下傳遞 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取目前的 theme context。
  // React 會往上找到最近的 theme Provider,然後使用它的值。
  // 在這個例子中,目前的 theme 值為 “danger”。
  static contextType = ThemeContext;
  render() {
    return <Button type={this.context}>context測試</Button>;
  }
}      

3、Redux

Redux 中文文檔[9]

redux 的三大核心:

  • action:action 可以說是一個動作,用來描述将要觸發的事件。
  • state:單一資料源,用來存儲我們的資料。
  • reducer:通過觸發的 action 事件來改變 state 的值。

不一定非要用,很多項目 context 就已經夠用了

(1)挂載

使用 ​

​createStore​

​​ 建立一個 store 并通過 ​

​Provider​

​ 把它放到容器元件中

// index.js
const store = createStore(appReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root");
);
      

(2)建立修改的方法

和 vuex 相似,都是通過 ​

​action​

​ 來修改資料

// action.js
export const addConst = (payload) => {
  type: "ADD_CONST",
}

export const minusConst = (payload) => {
 type: "MINUS_CONST",
}      

(3)建立一個 store 集合

當 dispatch 觸發相應的方法,執行對應的操作,修改 store 資料。

// appReducer.js
const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_CONST":
      return { count: count + 1 };
    case "MINUS_CONST":
      return { count: count - 1 };
    default:
      return state;
  }
};
export default reducer;      

(4)元件中 redux 使用姿勢

import React from "react";
import { connect } from "react-redux";

const ReduxDemo: React.FC = (props) => {
  const addCount = () {
    const { dispatch } = props;
    dispatch({
      type: "ADD_CONST",
    });
  };

  const minusCount = () {
    const { dispatch } = props;
    dispatch({
      type: "MINUS_CONST",
    });
  };
  return (
    <div>
      <button onClick={addCount}>加</button>
      <button onClick={minusCount}>減</button>
      <div>{props.state}</div>
    </div>
  );
};

const mapStateToProps = (state) => {
  return {
    count: state.count,
  };
};
export default connect(mapStateToProps)(ReduxDemo);      

六、元件校驗

React 官網 使用 PropTypes 進行類型檢查[10] react props 不是必須聲明的,但是如果項目規範起來,就需要在 propTypes 中聲明 props 類型,注意需要引入​

​prop-types​

​庫

不過現在更多的是通過 ​

​typescript​

​ 來校驗類型了,開發階段就能發現問題。

import * as React from "react";
import PropTypes from "prop-types";

interface IProps {
  name: string;
}

const PropsDemo: React.FC<IProps> = ({ name }) => {
  return <h1>Hello, {name}</h1>;
};

PropsDemo.propTypes = {
  name: PropTypes.string,
};      

七、React Router

  • React Router 官網[11] 英文版
  • React Router 中文文檔[12] 感覺寫的不是很清楚

1、注意

  • react-router: 實作了路由的核心功能, react-router 3.x  版本還包括操作 dom 的方法,4.x 以上就沒有了。
  • react-router-dom: 基于 react-router,加入了在浏覽器運作環境下的一些功能,例如:Link 元件,會渲染一個 a 标簽,Link 元件源碼 a 标簽行; BrowserRouter 和 HashRouter 元件,前者使用 pushState 和 popState 事件建構路由,後者使用 window.location.hash 和 hashchange 事件建構路由。
  • react-router-native: 基于 react-router,類似 react-router-dom,加入了 react-native 運作環境下的一些功能

2、一個 Demo

import React, { Component } from "react";
import Admin from "./pages/admin/admin";
import Login from "./pages/login/Login";
import { HashRouter, Route, Switch } from "react-router-dom";
class App extends Component {
  render() {
    return (
      <HashRouter>
        <Switch>
          <Route path="/" component={Admin}></Route>
          <Route path="/login" component={Login}></Route>
        </Switch>
      </HashRouter>
    );
  }
}

export default App;      

3、路由傳參

(1)params

// router
<Route path='/path/:id' component={Path}/>
// 傳參
<link to="/path/789">xxx</Link>
this.props.history.push({pathname:`/path/${id}`});
// 擷取
this.props.match.params.id      

(2)query

// router
<Route path='/query' component={Query}/>
// 傳參
<Link to={{ path : '/query' , query : { id : '789' }}}>xxx</Link>

this.props.history.push({pathname:"/query",query: { id : '789' }});
// 擷取
this.props.location.query.id      

(3)Hooks

// 跳轉
let history = useHistory();
history.push("/");

// 擷取
useLocation();
useParams();
useRouteMatch();      

4、exact 屬性

exact 是 Route 下的一條屬性,一般而言,react 路由會比對所有比對到的路由組價,exact 能夠使得路由的比對更嚴格一些。

exact 的值為 bool 型,為 true 是表示嚴格比對,為 false 時為正常比對。

如在 exact 為 true 時,’/link’與’/’是不比對的,但是在 false 的情況下它們又是比對的。​

​<Route path="/home" component={Home} exact></Route>​

八、總結

學完生命周期,多練習 JSX,配合 React Router 和 Redux 多寫寫元件,基本就能上手開發了。沒有過多的 API 需要學習,寫起來也比較自由。React 雖然生态強大,選着性比較多,但是這樣産生了一個問題:​

​什麼是 React 的最佳實踐?​

相關代碼倉庫:

倉庫:React-TypeScript

​​https://github.com/alexwjj/React-TypeScript​​

線上預覽:俊劫 React 學習系統

​​https://alexwjj.github.io/study​​

參考資料

[1]

React 生命周期圖解: ​​https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/​​

[2]

MDN 說明: ​​https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/super#%E6%8F%8F%E8%BF%B0​​

[3]

React-TypeScript 倉庫: ​​https://github.com/alexwjj/React-TypeScript​​

[4]

俊劫學習系統 LifeCycle: ​​https://alexwjj.github.io/study/#/demo​​

[5]

classnames: ​​https://github.com/JedWatson/classnames​​

[6]

代碼對應頁面預覽: ​​https://alexwjj.github.io/study/#/demo​​

[7]

高階元件: ​​https://zh-hans.reactjs.org/docs/higher-order-components.html​​

[8]

React Context 官網說明: ​​https://zh-hans.reactjs.org/docs/context.html​​

[9]

Redux 中文文檔: ​​http://cn.redux.js.org/​​

[10]

React 官網 使用 PropTypes 進行類型檢查: ​​https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html​​

[11]

React Router 官網: ​​https://reactrouter.com/web/guides/quick-start​​

[12]