后端到前端react實(shí)戰(zhàn)

1.背景

近期工作原因再次拾起react開發(fā),已經(jīng)將近兩年沒做過react的開發(fā)了,再次開始還是多少有很多需要重新熟悉的,但好在沒間斷過前端開發(fā),所以還是比較快上手的,但是難點(diǎn)在于怎么樣教會(huì)同組同事們學(xué)會(huì)react,快速上手。根據(jù)個(gè)人經(jīng)驗(yàn)總結(jié)出必須要會(huì)的部分,梳理文檔如下。

2.入門

按照個(gè)人經(jīng)驗(yàn)推薦學(xué)習(xí)順序如下
1.官網(wǎng)文檔手冊(cè):https://react.docschina.org/
2.生命周期學(xué)習(xí)
3.概念學(xué)習(xí)
4.hooks學(xué)習(xí)

3.生命周期

基本學(xué)習(xí)一個(gè)框架,我們首先我們需要了解其生命周期,之前跟大家普及vue的生命周期,就是兩年前使用react做業(yè)務(wù),踩過很多關(guān)于生命周期的坑。我們后端學(xué)習(xí)spring都還要學(xué)習(xí)其生命周期,前端也是一樣,能更好地控制接口調(diào)用時(shí)機(jī),組件展示的時(shí)機(jī)等等。

簡(jiǎn)單理解React為其組件定義了生命周期的三個(gè)狀態(tài)。針對(duì)這三個(gè)狀態(tài)提供了7種鉤子函數(shù),方便使用者在不同狀態(tài)之前或者之后設(shè)置一些事件監(jiān)聽或者邏輯處理。

Mounting:組件正在被插入到DOM節(jié)點(diǎn)中
Updating:組件正在被重新渲染,是否被更新取決于該組件是否有改變
Unmouting:組件正在從DOM節(jié)點(diǎn)中移出
針對(duì)以上三個(gè)狀態(tài),都分別提供了兩種鉤子函數(shù),用于在進(jìn)入這個(gè)狀態(tài)之前(will函數(shù)),活著離開這個(gè)狀態(tài)之后(did函數(shù))調(diào)用,理解了上面的狀態(tài),就會(huì)非常容易明白函數(shù)名和函數(shù)的調(diào)用時(shí)機(jī)了。

3.1React 生命周期(舊)

image.png

react舊版生命周期包含三個(gè)過程:

1、掛載過程
constructor()
componentWillMount()
componentDidMount()

2、更新過程
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps,nextState)
componentWillUpdate (nextProps,nextState)
render()
componentDidUpdate(prevProps,prevState)

3、卸載過程
componentWillUnmount()

其具體作用分別為:
1、constructor()
完成了React數(shù)據(jù)的初始化。

2、componentWillMount()
組件已經(jīng)完成初始化數(shù)據(jù),但是還未渲染DOM時(shí)執(zhí)行的邏輯,主要用于服務(wù)端渲染。

3、componentDidMount()
組件第一次渲染完成時(shí)執(zhí)行的邏輯,此時(shí)DOM節(jié)點(diǎn)已經(jīng)生成了。

4、componentWillReceiveProps(nextProps)
接收父組件新的props時(shí),重新渲染組件執(zhí)行的邏輯。

5、shouldComponentUpdate(nextProps, nextState)
在setState以后,state發(fā)生變化,組件會(huì)進(jìn)入重新渲染的流程時(shí)執(zhí)行的邏輯。在這個(gè)生命周期中return false可以阻止組件的更新,主要用于性能優(yōu)化。

6、componentWillUpdate(nextProps, nextState)
shouldComponentUpdate返回true以后,組件進(jìn)入重新渲染的流程時(shí)執(zhí)行的邏輯。

7、render()
頁(yè)面渲染執(zhí)行的邏輯,render函數(shù)把jsx編譯為函數(shù)并生成虛擬dom,然后通過其diff算法比較更新前后的新舊DOM樹,并渲染更改后的節(jié)點(diǎn)。

8、componentDidUpdate(prevProps, prevState)
重新渲染后執(zhí)行的邏輯。

9、componentWillUnmount()
組件的卸載前執(zhí)行的邏輯,比如進(jìn)行“清除組件中所有的setTimeout、setInterval等計(jì)時(shí)器”或“移除所有組件中的監(jiān)聽器removeEventListener”等操作。

3.2React生命周期(新)

image.png

react16.4后使用了新的生命周期,使用getDerivedStateFromProps代替了舊的componentWillReceiveProps及componentWillMount。使用getSnapshotBeforeUpdate代替了舊的componentWillUpdate。

使用getDerivedStateFromProps(nextProps, prevState)的原因:
舊的React中componentWillReceiveProps方法是用來判斷前后兩個(gè) props 是否相同,如果不同,則將新的 props 更新到相應(yīng)的 state 上去。在這個(gè)過程中我們實(shí)際上是可以訪問到當(dāng)前props的,這樣我們可能會(huì)對(duì)this.props做一些奇奇怪怪的操作,很可能會(huì)破壞 state 數(shù)據(jù)的單一數(shù)據(jù)源,導(dǎo)致組件狀態(tài)變得不可預(yù)測(cè)。

而在 getDerivedStateFromProps 中禁止了組件去訪問 this.props,強(qiáng)制讓開發(fā)者去比較 nextProps 與 prevState 中的值,以確保當(dāng)開發(fā)者用到 getDerivedStateFromProps 這個(gè)生命周期函數(shù)時(shí),就是在根據(jù)當(dāng)前的 props 來更新組件的 state,而不是去訪問this.props并做其他一些讓組件自身狀態(tài)變得更加不可預(yù)測(cè)的事情。

使用getSnapshotBeforeUpdate(prevProps, prevState)的原因:
在 React 開啟異步渲染模式后,在執(zhí)行函數(shù)時(shí)讀到的 DOM 元素狀態(tài)并不總是渲染時(shí)相同,這就導(dǎo)致在 componentDidUpdate 中使用 componentWillUpdate 中讀取到的 DOM 元素狀態(tài)是不安全的,因?yàn)檫@時(shí)的值很有可能已經(jīng)失效了。

而getSnapshotBeforeUpdate 會(huì)在最終的 render 之前被調(diào)用,也就是說在 getSnapshotBeforeUpdate 中讀取到的 DOM 元素狀態(tài)是可以保證與componentDidUpdate 中一致的。

4.概念

4.1JSX語法

使用JSX語法,可以定義簡(jiǎn)潔而且較為熟知的樹狀語法結(jié)構(gòu)。其實(shí)它的基本語法規(guī)則也很簡(jiǎn)單:遇到HTML標(biāo)簽(<開頭,并且第一個(gè)字母是小寫,如<div>),就用HTML規(guī)則解析;遇到代碼塊(以{開口)就用JavaScript規(guī)則解析,遇到組件(<開頭,并且第一個(gè)字母是大寫,如<Comment>),就是我們的React組件的類名了,所以寫組件類的時(shí)候,別忘了類名以大寫字母開頭。

 var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
          <h1>Comments</h1>
          <CommentList data={this.props.data} />
          <CommentForm />
      </div>
    );
  }
});

4.2Components

Component就是其實(shí)就是React的核心思想,它通過把代碼封裝成組件的形式,然后每調(diào)用一次就會(huì)通過React的工廠方法來生成這個(gè)組件類的實(shí)例,并且根據(jù)注入的props或者state的值來輸出組件。

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

ReactDOM.render是React的最基本用法,用于將模版轉(zhuǎn)為HTML語言,并插入指定的DOM節(jié)點(diǎn)中。下面兩小節(jié)代碼將展示如何將值傳到組件中,組件如何獲取。

4.3Props

通過Props屬性,組件能夠讀取到從父組件傳遞過來的數(shù)據(jù),然后通過這些標(biāo)記渲染一些標(biāo)記,所有在父組件中傳過來的屬性都可以通過this.props.propertyName來獲取到,其中有一個(gè)特殊的屬性this.props.children,通過它你可以獲取到組件的所有子節(jié)點(diǎn),如下例所示:

var data = [
        {author: "Pete Hunt", text: "This is one comment"},
        {author: "Jordan Walke", text: "This is *another* comment"}
      ];
      var Comment = React.createClass({
        render: function() {
          return (
            <div className="comment">
              <h2 className="commentAuthor">
                {this.props.author}
              </h2>
              {this.props.children}
            </div>
          );
        }
      });
      var CommentList = React.createClass({
        render: function() {
          return (
            <div className="commentList">
              <Comment author="Pete Hunt">This is one comment</Comment>
              <Comment author="Jordan Walke">This is *another* comment</Comment>
            </div>
          );
        }
      });
      var CommentBox = React.createClass({
        render: function() {
          return (
            <div className="commentBox">
                <h1>Comments</h1>
                <CommentList data={this.props.data} />
            </div>
          );
        }
      });
      ReactDOM.render(
        <CommentBox data={data} />,
        document.getElementById('content')
      );

image

綠色框是由Comment組件負(fù)責(zé)生成的,它被它的父組件CommentList(圖中的藍(lán)色框)調(diào)用了兩次,所以根據(jù)props中獲取的不同的數(shù)據(jù)實(shí)例化了兩次。接著組件CommentList又被頂層組件CommentBox所包括(圖中的紅色框)。React就是通過這樣的方式,將組件與組件之間的關(guān)系建立起來的,通過組合,可以做出各式各樣的我們需要的頁(yè)面。同時(shí),由于這些模塊化的組件,使得我們可以只關(guān)注傳入組件和改變組件的數(shù)據(jù),基本數(shù)據(jù)對(duì)了,組件對(duì)數(shù)據(jù)的渲染也就對(duì)了。同時(shí)我們也可以在后續(xù)的開發(fā)中,將一些通用的組件抽出來,代碼結(jié)構(gòu)清晰有調(diào)理。隨著開發(fā)的不斷深入和代碼的不斷累積,這種優(yōu)勢(shì)就會(huì)越來越明顯。

4.4State

在上一節(jié)的例子中,在組件CommentList中傳給Comment組件是寫死的。我們知道,可以通過父組設(shè)置屬性,然后子組件中通過props獲取。但是如果子組件中的數(shù)據(jù)會(huì)不斷地改變(或者通過定時(shí)器,或者通過回調(diào),或者通過Ajax),子組件如何通過數(shù)據(jù)的變化來不斷地重新渲染呢?答案是State。

state和props一樣,都是用來描述組件的特性。只不過不同的是,對(duì)于props屬性,組件只會(huì)在對(duì)象實(shí)例的時(shí)候渲染并返回render函數(shù),而對(duì)state的設(shè)置則在組件的生命周期內(nèi)都有效,只要setState了,組件就會(huì)重新渲染并返回render。換而言之,就是如果你在組件實(shí)例以后再對(duì)props進(jìn)行更新,react并不能保證你的更新會(huì)反應(yīng)到VDOM甚至DOM上,而setState就可以。所以我們一般將哪些定義了以后就不再改變的特性放在props中,而隨著用戶交互或者定時(shí)觸發(fā)產(chǎn)生變化的一些特性,那放在state中將是更好的選擇。
現(xiàn)在,我們添加一個(gè)可以供用戶輸入的兩個(gè)輸入框和一個(gè)按鈕,讓用戶來輸入自己的名字和評(píng)論內(nèi)容,點(diǎn)擊提交后頁(yè)面將會(huì)顯示他們的評(píng)論。

      //修改CommentList
      var CommentList = React.createClass({
        render: function() {
          var commentNodes = this.props.data.map(function (comment) {
            return (
              <Comment author={comment.author}>
                {comment.text}
              </Comment>
            );
          });
          return (
            <div className="commentList">
              {commentNodes}
            </div>
          );
        }
      });
      //創(chuàng)建CommentForm組件,用于用戶輸入提交
      var CommentForm = React.createClass({
        handleSubmit: function(e) {
          e.preventDefault();
          var author = this.refs.author.value.trim();
          var text = this.refs.text.value.trim();
          if (!text || !author) {
            return;
          }
          this.props.onCommentSubmit({author: author, text: text});
          this.refs.author.value = "";
          this.refs.text.value = "";
          alert("Submit!");
          return;
        },
        render: function() {
          return (
            <form className="commentForm" onSubmit={this.handleSubmit}>
              <input type="text" placeholder="Your name" ref="author" />
              <input type="text" placeholder="Say something..." ref="text" />
              <input type="submit" value="Post" />
            </form>
          );
        }
      });
      var data = [
        {author: "YYQ", text: "這是一條評(píng)論"},
        {author: "wuqke", text: "這是另外一條評(píng)論"}
      ];
      var Comment = React.createClass({
        render: function() {
          return (
            <div className="comment">
              <h3 className="commentAuthor">
                {this.props.author}說:
              </h3>
              <span>{this.props.children}</span>
            </div>
          );
        }
      });
      var CommentList = React.createClass({
        render: function() {
          var commentNodes = this.props.data.map(function (comment) {
            return (
              <Comment author={comment.author}>
                {comment.text}
              </Comment>
            );
          });
          return (
            <div className="commentList">
              {commentNodes}
            </div>
          );
        }
      });
      var CommentForm = React.createClass({
        handleSubmit: function(e) {
          e.preventDefault();
          var author = this.refs.author.value.trim();
          var text = this.refs.text.value.trim();
          if (!text || !author) {
            return;
          }
          this.props.onCommentSubmit({author: author, text: text});
          this.refs.author.value = "";
          this.refs.text.value = "";
          alert("Submit!");
          return;
        },
        render: function() {
          return (
            <form className="commentForm" onSubmit={this.handleSubmit}>
              <input type="text" placeholder="Your name" ref="author" />
              <input type="text" placeholder="Say something..." ref="text" />
              <input type="submit" value="Post" />
            </form>
          );
        }
      });
      var CommentBox = React.createClass({
        getInitialState: function() {
          return {data: []};
        },handleCommentSubmit: function(comment) {
          var ndata = this.state.data;
          ndata.push(comment);
          this.setState({data:ndata});
        },
        componentDidMount: function() {
          this.setState({data:this.props.data})
        },
        render: function() {
          return (
            <div className="commentBox">
              <h1>Comments</h1>
              <CommentList data={this.state.data} />
              <CommentForm  onCommentSubmit={this.handleCommentSubmit}/>
            </div>
          );
        }
      });
      ReactDOM.render(
        <CommentBox data={data} />,
        document.getElementById('content')
      );
      //修改CommentBox組件,當(dāng)回調(diào)函數(shù)被觸發(fā)的時(shí)候,將comment添加到data中并且setState更新data
      var CommentBox = React.createClass({
        getInitialState: function() {
          return {data: []};
        },handleCommentSubmit: function(comment) {
          var ndata = this.state.data;
          ndata.push(comment);
          this.setState({data:ndata});
        },
        componentDidMount: function() {
          this.setState({data:this.props.datas})
        },
        render: function() {
          return (
            <div className="commentBox">
              <h1>Comments</h1>
              <CommentList data={this.state.data} />
              <CommentForm  onCommentSubmit={this.handleCommentSubmit}/>
            </div>
          );
        }
      });

image

----------------------------------------

image

上面的例子,在CommentBox的render中添加了一個(gè)CommentForm組件,用于獲取用戶的輸入,同時(shí)添加了一個(gè)函數(shù)handleCommentSubmit(comment),函數(shù)接收comment參數(shù),做的事情很簡(jiǎn)單,就是和原來的數(shù)據(jù)data合并,并通過setState()更新數(shù)據(jù)data,該組件發(fā)現(xiàn)state變化以后,就會(huì)去重新渲染組件,最后在執(zhí)行render函數(shù),最終將變化反映在VDOM和DOM上。這讓我們可以只關(guān)注數(shù)據(jù)的變化,而不必去考慮太多DOM節(jié)點(diǎn)是否被更新的問題。

其中最難理解的就是props和state。簡(jiǎn)單區(qū)別理解我總結(jié)如下:
1.props不可變, state可變會(huì)觸發(fā)組件渲染并且state值更新是異步的,異步的,異步的!
因?yàn)椋?br> state是組件自己管理數(shù)據(jù),控制自己的狀態(tài),可變,多是在組件中創(chuàng)建,一般是在constructor中初始化state。每次setState都是異步更新的。
props是外部傳入的數(shù)據(jù)參數(shù),不可變,所有的react組件都必須像純函數(shù)一樣保護(hù)他們的props不被修改。
沒有state的叫做無狀態(tài)組件,有state的叫做有狀態(tài)組件;
多用props,少用state。也就是多寫無狀態(tài)組件。

2.props一般用于父組件向子組件通信,在組件之間通信使用。state一般用于組件內(nèi)部的狀態(tài)維護(hù),更新組建內(nèi)部的數(shù)據(jù),狀態(tài),更新子組件的props等。父組件的state常常轉(zhuǎn)變子組件的props。

3.props是傳遞給組件的(類似于函數(shù)的形參),而state是在組件內(nèi)部被組件自己管理的(類似于在一個(gè)函數(shù)內(nèi)聲明的變量)。

5.hook

Hook 是一些可以讓你在函數(shù)組件里“鉤子函數(shù)” React state 及生命周期等特性的函數(shù)。在系統(tǒng)沒有調(diào)用該函數(shù)之前,鉤子程序就先捕獲該消息,鉤子函數(shù)先得到控制權(quán),這時(shí)鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為,還可以強(qiáng)制結(jié)束消息的傳遞。簡(jiǎn)單來說,就是把系統(tǒng)的程序拉出來變成我們自己執(zhí)行代碼片段。
??要實(shí)現(xiàn)鉤子函數(shù),有兩個(gè)步驟:
??1. 利用系統(tǒng)內(nèi)部提供的接口,通過實(shí)現(xiàn)該接口,然后注入進(jìn)系統(tǒng)(特定場(chǎng)景下使用)
??2.動(dòng)態(tài)代理(使用所有場(chǎng)景)

常用的hooks有以下幾種:

  • useState,useEffect,useRef,useContext
  • useSelector,useDispatch
  • 自定義hooks

5.1 使用 State Hook

官方文檔

示例:點(diǎn)擊按鈕,使得按鈕的文案由"點(diǎn)擊前"變?yōu)椤包c(diǎn)擊后”。

傳統(tǒng)的寫法:

import React, { Component } from "react";
 
export default class Button extends Component {
    constructor() {
        super();
        this.state = {
            buttonText: "點(diǎn)擊前"
        };
    }
    handleClick() {
        this.setState({
            'buttonText': "點(diǎn)擊后"
        });
    }
    render() {
        const { buttonText } = this.state;
        return <button onClick={() => this.handleClick()}>{buttonText}</button>;
    }
}

使用hooks的寫法:函數(shù)組件不需要構(gòu)造函數(shù),可以通過調(diào)用 useState 來初始化 state

import React, { useState } from "react";
 
export default function Button() {
    const [buttonText,setButtonText] = useState("點(diǎn)擊前");
    const handleClick = () => {
        setButtonText("點(diǎn)擊后");
    }
    return <button onClick={handleClick}>{buttonText}</button>;
}

5.2.使用 Effect Hook

官方文檔

useEffect 擁有兩個(gè)參數(shù),第一個(gè)參數(shù)作為回調(diào)函數(shù)會(huì)在瀏覽器布局和繪制完成后調(diào)用,因此它不會(huì)阻礙瀏覽器的渲染進(jìn)程。
第二個(gè)參數(shù)是一個(gè)數(shù)組

  • 當(dāng)數(shù)組存在并有值時(shí),如果數(shù)組中的任何值發(fā)生更改,則每次渲染后都會(huì)觸發(fā)回調(diào)。
  • 當(dāng)它不存在時(shí),每次渲染后都會(huì)觸發(fā)回調(diào)。
  • 當(dāng)它是一個(gè)空列表時(shí),回調(diào)只會(huì)被觸發(fā)一次,類似于 componentDidMount。

示例:新建一個(gè)定時(shí)器,每秒數(shù)量+1

傳統(tǒng)的寫法:

import React, { Component } from 'react';
class Count extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 1,
        }
    }
    componentDidMount() {
        // 新建一個(gè)定時(shí)器實(shí)現(xiàn)+1操作
        this.timer = setInterval(() => {
            this.setState({
                count: this.state.count - 0 + 1
            })
        }, 1000);
    }
    componentWillUnmount() {
        // 組件銷毀時(shí)清除定時(shí)器,否則會(huì)造成內(nèi)存泄漏
        clearInterval(this.timer)
    }
    render() {
        return <div>
            {this.state.count}
        </div>
    }
}
export default Count;

hooks的寫法:

import React, { useState, useEffect } from 'react';
 
const Count = (props) => {
    const [count, setCount] = useState(0);
 
    useEffect(() => {
        let timer = setInterval(() => {
            setCount(count + 1)
        }, 1000);
        // 當(dāng)在 useEffect 的回調(diào)函數(shù)中返回一個(gè)函數(shù)時(shí),這個(gè)函數(shù)會(huì)在組件卸載前被調(diào)用
        return () => clearInterval(timer)
        // count發(fā)生變化時(shí)再次執(zhí)行
    }, [count]);
 
    return <div>
        {count}
    </div>
}
export default Count;

5.3 使用 useRef

useRef 返回一個(gè)可變的 ref 對(duì)象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變。

示例:獲取某個(gè)元素

傳統(tǒng)寫法:

import React, { Component } from 'react'
 
export default class Test extends Component {
    componentDidMount() {
        console.log(this.refs.box)
    }
    render() {
        return (
            <div ref="box">
                this is a box
            </div>
        )
    }
}

hooks寫法:

import React, { useRef, useEffect } from "react";
 
export default function Test() {
    // 初始化一個(gè)useRef
    const box = useRef(null);
 
    useEffect(() => {
        // 通過.current獲取
        console.log(box.current)
    }, [])
 
    return (
        <div ref={box}>this is a box</div>
    );
}

5.4 使用 自定義hook

官方文檔
自定義 Hook 是一個(gè)函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook。

示例:封裝一個(gè)組件,實(shí)現(xiàn)鼠標(biāo)滑過顯示鼠標(biāo)所在位置

傳統(tǒng)方式:定義一個(gè)高階組件,使用時(shí)將組件作為參數(shù)傳入高階組件

/**
 * 定義一個(gè)高階組件
 * 返回鼠標(biāo)所在位置
 */
 
import React from 'react'
export default (Component) => {
    return class WrappedComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                positionX: 0,
                positionY: 0
            }
        }
        componentDidMount() {
            document.addEventListener('mousemove', (e) => {
                this.setState({
                    positionX: e.clientX,
                    positionY: e.clientY
                })
            }) // 在這里我們更新鼠標(biāo)的位置,并存儲(chǔ)在state中去,然后通過props傳遞給被傳入的組件
        }
        render() {
            return (
                <Component {...this.props} {...this.state} {...{ 'x': 1 }} />
                //props:這里返回的是WrappedComponent這個(gè)組件,所以本應(yīng)該傳遞給Component組件的props,我們應(yīng)該通過WrappedComponent傳遞下去
                //state: WrappedComponent可以操作自己的狀態(tài),我們可以將這些狀態(tài)通過props的方式傳遞給Component組件
            )
        }
 
    }
}
/**
 * 使用時(shí)將組件作為參數(shù)傳入高階組件
 */
import React from 'react';
import mousePositionHoc from './hoc';
class MousePoint extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <div>
                <span>x:{this.props.positionX}</span>
                <span>y:{this.props.positionY}</span>
            </div>
        )
    }
}
export default mousePositionHoc(MousePoint)

hooks:自定義一個(gè)hooks,使用時(shí)直接引入。

/**
 * 自定義一個(gè)hooks
 * 返回鼠標(biāo)所在位置
 */
import React, { useState, useEffect } from 'react'
 
const useMousePosition = () => {
    const [position, setPosition] = useState({ x: 0, y: 0 })
    useEffect(() => {
        const updateMouse = (e) => {
            setPosition({ x: e.clientX, y: e.clientY })
        }
        document.addEventListener('mousemove', updateMouse)
    }, [])
    return position
}
 
export default useMousePosition
 
 
//使用時(shí)引入自定義hooks
import React from 'react'
import useMousePosition from './hooks'
 
export default function App() {
    const position = useMousePosition()
    return (
        <div>
            <div>x: {position.x}</div>
            <div>y: {position.y}</div>
        </div>
    )
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容