国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

一只node爬蟲的升級打怪之路

shiweifu / 1419人閱讀

摘要:我是一個知乎輕微重度用戶,之前寫了一只爬蟲幫我爬取并分析它的數(shù)據(jù),我感覺這個過程還是挺有意思,因?yàn)檫@是一個不斷給自己創(chuàng)造問題又去解決問題的過程。所以這只爬蟲還有登陸知乎搜索題目的功能。

我一直覺得,爬蟲是許多web開發(fā)人員難以回避的點(diǎn)。我們也應(yīng)該或多或少的去接觸這方面,因?yàn)榭梢詮呐老x中學(xué)習(xí)到web開發(fā)中應(yīng)當(dāng)掌握的一些基本知識。而且,它還很有趣。

我是一個知乎輕微重度用戶,之前寫了一只爬蟲幫我爬取并分析它的數(shù)據(jù),我感覺這個過程還是挺有意思,因?yàn)檫@是一個不斷給自己創(chuàng)造問題又去解決問題的過程。其中遇到了一些點(diǎn),今天總結(jié)一下跟大家分享分享。

它都爬了什么?

先簡單介紹下我的爬蟲。它能夠定時(shí)抓取一個問題的關(guān)注量、瀏覽量、回答數(shù),以便于我將這些數(shù)據(jù)繪成圖表展現(xiàn)它的熱點(diǎn)趨勢。為了不讓我錯過一些熱門事件,它還會定時(shí)去獲取我關(guān)注話題下的熱門問答,并推送到我的郵箱。

作為一個前端開發(fā)人員,我必須為這個爬蟲系統(tǒng)做一個界面,能讓我登陸知乎帳號,添加關(guān)注的題目、話題,看到可視化的數(shù)據(jù)。所以這只爬蟲還有登陸知乎、搜索題目的功能。

然后來看下界面。

下面正兒八經(jīng)講它的開發(fā)歷程。

技術(shù)選型

Python得益于其簡單快捷的語法、以及豐富的爬蟲庫,一直是爬蟲開發(fā)人員的首選。可惜我不熟。當(dāng)然最重要的是,作為一名前端開發(fā)人員,node能滿足爬蟲需求的話,自然更是首選。而且隨著node的發(fā)展,也有許多好用的爬蟲庫,甚至有puppeteer這樣直接能模擬Chrome訪問網(wǎng)頁的工具的推出,node在爬蟲方面應(yīng)該是妥妥能滿足我所有的爬蟲需求了。

于是我選擇從零搭建一個基于koa2的服務(wù)端。為什么不直接選擇egg,express,thinkjs這些更加全面的框架呢?因?yàn)槲覑壅垓v嘛。而且這也是一個學(xué)習(xí)的過程。如果以前不了解node,又對搭建node服務(wù)端有興趣,可以看我之前的一篇文章-從零搭建Koa2 Server。

爬蟲方面我選擇了request+cheerio。雖然知乎有很多地方用到了react,但得益于它絕大部分頁面還是服務(wù)端渲染,所以只要能請求網(wǎng)頁與接口(request),解析頁面(cherrio)即可滿足我的爬蟲需求。

其他不一一舉例了,我列個技術(shù)棧

服務(wù)端

koajs 做node server框架;

request + cheerio 做爬蟲服務(wù);

mongodb 做數(shù)據(jù)存儲;

node-schedule 做任務(wù)調(diào)度;

nodemailer 做郵件推送。

客戶端

vuejs 前端框架;

museui Material Design UI庫;

chart.js 圖表庫。

技術(shù)選型妥善后,我們就要關(guān)心業(yè)務(wù)了。首要任務(wù)就是真正的爬取到頁面。

如何能爬取網(wǎng)站的數(shù)據(jù)?

知乎并沒有對外開放接口能讓用戶獲取數(shù)據(jù),所以想獲取數(shù)據(jù),就得自己去爬取網(wǎng)頁信息。我們知道即使是網(wǎng)頁,它本質(zhì)上也是個GET請求的接口,我們只要在服務(wù)端去請求對應(yīng)網(wǎng)頁的地址(客戶端請求會跨域),再把html結(jié)構(gòu)解析下,獲取想要的數(shù)據(jù)即可。

那為什么我要搞一個登陸呢?因?yàn)榉堑顷憥ぬ柅@取信息,知乎只會展現(xiàn)有限的數(shù)據(jù),而且也無法得知自己知乎帳戶關(guān)注的話題、問題等信息。而且若是想自己的系統(tǒng)也給其他朋友使用,也必須搞一個帳戶系統(tǒng)。

模擬登陸

大家都會用Chrome等現(xiàn)代瀏覽器看請求信息,我們在知乎的登錄頁進(jìn)行登陸,然后查看捕獲接口信息就能知道,登陸無非就是向一個登陸api發(fā)送賬戶、密碼等信息,如果成功。服務(wù)端會向客戶端設(shè)置一個cookie,這個cookie即是登陸憑證。

所以我們的思路也是如此,通過爬蟲服務(wù)端去請求接口,帶上我們的帳號密碼信息,成功后再將返回的cookie存到我們的系統(tǒng)數(shù)據(jù)庫,以后再去爬取其他頁面時(shí),帶上此cookie即可。

當(dāng)然,等我們真正嘗試時(shí),會受到更多挫折,因?yàn)闀龅絫oken、驗(yàn)證碼等問題。不過,由于我們有客戶端了,可以將驗(yàn)證碼的識別交給真正的,而不是服務(wù)端去解析圖片字符,這降低了我們實(shí)現(xiàn)登陸的難度。

一波三折的是,即使你把正確驗(yàn)證碼提交了,還是會提示驗(yàn)證碼錯誤。如果我們自己做過驗(yàn)證碼提交的系統(tǒng)就能夠迅速的定位原因。如果沒做過,我們再次查看登陸時(shí)涉及的請求與響應(yīng),我們也能猜到:

在客戶端獲取驗(yàn)證碼時(shí),知乎服務(wù)端還會往客戶端設(shè)置一個新cookie,提交登陸請求時(shí),必須把驗(yàn)證碼與此cookie一同提交,來驗(yàn)證此次提交的驗(yàn)證碼確實(shí)是當(dāng)時(shí)給予用戶的驗(yàn)證碼。

語言描述有些繞,我以圖的形式來表達(dá)一個登陸請求的完整流程。

注:我編寫爬蟲時(shí),知乎還部分采取圖片字符驗(yàn)證碼,現(xiàn)已全部改為“點(diǎn)擊倒立文字”的形式。這樣會加大提交正確驗(yàn)證碼的難度,但也并非無計(jì)可施。獲取圖片后,由人工識別并點(diǎn)擊倒立文字,將點(diǎn)擊的坐標(biāo)提交到登陸接口即可。當(dāng)然有興趣有能力的同學(xué)也可以自己編寫算法識別驗(yàn)證碼。

爬取數(shù)據(jù)

上一步中,我們已經(jīng)獲取到了登陸后的憑證cookie。用戶登陸成功后,我們把登陸的帳戶信息與其憑證cookie存到mongo中。以后此用戶發(fā)起的爬取需求,包括對其跟蹤問題的數(shù)據(jù)爬取都根據(jù)此cookie爬取。

當(dāng)然cookie是有時(shí)間期限的,所以當(dāng)我們存cookie時(shí),應(yīng)該把過期時(shí)間也記錄下來,當(dāng)后面再獲取此cookie時(shí),多加一步過期校驗(yàn),若過期了則返回過期提醒。

爬蟲的基礎(chǔ)搞定后,就可以真正去獲取想要的數(shù)據(jù)了。我的需求是想知道某個知乎問題的熱點(diǎn)趨勢。先用瀏覽器去看看一個問題頁面下都有哪些數(shù)據(jù),可以被我爬取分析。舉個例子,比如這個問題:有哪些令人拍案叫絕的推理橋段。

打開鏈接后,頁面上最直接展現(xiàn)出來的有關(guān)注者被瀏覽1xxxx個回答,還要默認(rèn)展示的幾個高贊回答及其點(diǎn)贊評論數(shù)量。右鍵查看網(wǎng)站源代碼,確認(rèn)這些數(shù)據(jù)是服務(wù)端渲染出來的,我們就可以通過request請求網(wǎng)頁,再通過cherrio,使用css選擇器定位到數(shù)據(jù)節(jié)點(diǎn),獲取并存儲下來。代碼示例如下:

async getData (cookie, qid) {
  const options = {
    url: `${zhihuRoot}/question/${qid}`,
    method: "GET",
    headers: {
      "Cookie": cookie,
      "Accept-Encoding": "deflate, sdch, br" // 不允許gzip,開啟gzip會開啟知乎客戶端渲染,導(dǎo)致無法爬取
    }
  }
  const rs = await this.request(options)
  if (rs.error) {
    return this.failRequest(rs)
  }
  const $ = cheerio.load(rs)
  const NumberBoard = $(".NumberBoard-item .NumberBoard-value")
  const $title = $(".QuestionHeader-title")
  $title.find("button").remove()
  return {
    success: true,
    title: $title.text(),
    data: {
      qid: qid,
      followers: Number($(NumberBoard[0]).text()),
      readers: Number($(NumberBoard[1]).text()),
      answers: Number($("h4.List-headerText span").text().replace(" 個回答", ""))
    }
  }
}

這樣我們就爬取了一個問題的數(shù)據(jù),只要我們能夠按一定時(shí)間間隔不斷去執(zhí)行此方法獲取數(shù)據(jù),最終我們就能繪制出一個題目的數(shù)據(jù)曲線,分析起熱點(diǎn)趨勢。

那么問題來了,如何去做這個定時(shí)任務(wù)呢?

定時(shí)任務(wù)

我使用了node-schedule做任務(wù)調(diào)度。如果之前做過定時(shí)任務(wù)的同學(xué),可能對其類似cron的語法比較熟悉,不熟悉也沒關(guān)系,它提供了not-cron-like的,更加直觀的設(shè)置去配置任務(wù),看下文檔就能大致了解。

當(dāng)然這個定時(shí)任務(wù)不是簡單的不斷去執(zhí)行上述的爬取方法getData。因?yàn)檫@個爬蟲系統(tǒng)不僅是一個用戶,一個用戶不僅只跟蹤了一個問題。

所以我們此處的完整任務(wù)應(yīng)該是遍歷系統(tǒng)的每個cookie未過期用戶,再遍歷每個用戶的跟蹤問題,再去獲取這些問題的數(shù)據(jù)。

系統(tǒng)還有另外兩個定時(shí)任務(wù),一個是定時(shí)爬取用戶關(guān)注話題的熱門回答,另一個是推送這個話題熱門回答給相應(yīng)的用戶。這兩個任務(wù)跟上述任務(wù)大致流程一樣,就不細(xì)講了。

但是在我們做定時(shí)任務(wù)時(shí)會有個細(xì)節(jié)問題,就是如何去控制爬取時(shí)的并發(fā)問題。具體舉例來說:如果爬蟲請求并發(fā)太高,知乎可能是會限制此IP的訪問的,所以我們需要讓爬蟲請求一個一個的,或者若干個若干個的進(jìn)行。

簡單思考下,我們會采取循環(huán)await。我不假思索的寫下了如下代碼:

// 爬蟲方法
async function getQuestionData () {
  // do spider action
}

// questions為獲取到的關(guān)注問答
questions.forEach(await getQuestionData)

然而執(zhí)行之后,我們會發(fā)現(xiàn)這樣其實(shí)還是并發(fā)執(zhí)行的,為什么呢?其實(shí)仔細(xì)想下就明白了。forEach只是循環(huán)的語法糖,如果沒有這個方法,讓你來實(shí)現(xiàn)它,你會怎么寫呢?你大概也寫的出來:

Array.prototype.forEach = function (callback) {
  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this)
  }
}

雖然forEach本身會更復(fù)雜點(diǎn),但大致就是這樣吧。這時(shí)候我們把一個異步方法作為參數(shù)callback傳遞進(jìn)去,然后循環(huán)執(zhí)行它,這個執(zhí)行依舊是并發(fā)執(zhí)行,并非是同步的。

所以我們?nèi)绻雽?shí)現(xiàn)真正的同步請求,還是需要用for循環(huán)去執(zhí)行,如下:

async function getQuestionData () {
  // do spider action
}
for (let i = 0; i < questions.length; i++) {
  await getQuestionData()
}

除了for循環(huán),還可以通過for-of,如果對這方面感興趣,可以去多了解下數(shù)組遍歷的幾個方法,順便研究下ES6的迭代器Iterator

其實(shí)如果業(yè)務(wù)量大,即使這樣做也是不夠的。還需要更加細(xì)分任務(wù)顆粒度,甚至要加代理IP來分散請求。

合理搭建服務(wù)端

下面說的點(diǎn)跟爬蟲本身沒有太大關(guān)系了,屬于服務(wù)端架構(gòu)的一些分享,如果只關(guān)心爬蟲本身的話,可以不用再往下閱讀了。

我們把爬蟲功能都寫的差不多了,后面只要編寫相應(yīng)的路由,能讓前端訪問到數(shù)據(jù)就好了。但是編寫一個沒那么差勁的服務(wù)端,還是需要我們深思熟慮的。

合理分層

我看過一些前端同學(xué)寫的node服務(wù),經(jīng)常就會把系統(tǒng)所有的接口(router action)都寫到一個文件中,好一點(diǎn)的會根據(jù)模塊分幾個對于文件。

但是如果我們接觸過其他成熟的后端框架、或者大學(xué)學(xué)過一些J2EE等知識,就會本能意識的進(jìn)行一些分層:

model 數(shù)據(jù)層。負(fù)責(zé)數(shù)據(jù)持久化,通俗說就是連接數(shù)據(jù)庫,對應(yīng)數(shù)據(jù)庫表的實(shí)體數(shù)據(jù)模型;

service 業(yè)務(wù)邏輯層。顧名思義,就是負(fù)責(zé)實(shí)現(xiàn)各種業(yè)務(wù)邏輯。

controller 控制器。調(diào)取業(yè)務(wù)邏輯服務(wù),實(shí)現(xiàn)數(shù)據(jù)傳遞,返回客戶端視圖或數(shù)據(jù)。

當(dāng)然也有些框架或者人會將業(yè)務(wù)邏輯service實(shí)現(xiàn)在controller中,亦或者是model層中。我個人認(rèn)為一個稍微復(fù)雜的項(xiàng)目,應(yīng)該是多帶帶抽離出抽象的業(yè)務(wù)邏輯的。

比如在我這個爬蟲系統(tǒng)中,我將數(shù)據(jù)庫的添刪改查操作按model層對應(yīng)抽離出service,另外再將爬取頁面的服務(wù)、郵件推送的服務(wù)、用戶鑒權(quán)的服務(wù)抽離到對應(yīng)的service

最終我們的api能夠設(shè)計(jì)的更加易讀,整個系統(tǒng)也更加易拓展。

分層在koa上的實(shí)踐

如果是直接使用一個成熟的后端框架,分層這事我們是不用多想的。node這樣的框架也有,我之前介紹的我廠開源的api-mocker采用的egg.js,也幫我們做好了合理的分層。

但是如果自己基于koa從零搭建一個服務(wù)端,在這方面上就會遇到一些挫折。koa本身邏輯非常簡單,就是調(diào)取一系列中間件(就是一個個function),來處理請求。官方自己提供的koa-router,即是幫助我們識別請求路徑,然后加載對應(yīng)的接口方法。

我們?yōu)榱藚^(qū)分業(yè)務(wù)模塊,會把一些接口方法寫在同一個controller中,比如我的questionController負(fù)責(zé)處理問題相關(guān)的接口;topicController負(fù)責(zé)處理話題相關(guān)的接口。

那么我們可能會這樣編寫路由文件:

const Router = require("koa-router")
const router = new Router()

const question = require("./controller/question")
const topic = require("./controller/topic")

router.post("/api/question", question.create)
router.get("/api/question", question.get)

router.get("/api/topic", topic.get)
router.post("/api/topic/follow", topic.follow)

module.exports = router

我的question文件可能是這樣寫的:

class Question {
  async get () {
    // return data
  }
  async create () {
    // create question and return data
  }
}

module.exports = new Question()
那么問題就來了

單純這樣寫是沒有辦法真正的以面向?qū)ο蟮男问絹砭帉?b>controller的。為什么呢?

因?yàn)槲覀儗uestion對象的屬性方法作為中間件傳遞到了koa-router中,然后由koa底層來合并這些中間件方法,作為參數(shù)傳遞到http.createServer方法中,最終由node底層監(jiān)聽請求時(shí)調(diào)用。那這個this到底會是誰,不進(jìn)行調(diào)試,或者查看koa與node源代碼,是無從得知的。但是無論如何方法調(diào)用者肯定不是這個對象自身了(實(shí)際上它會是undefined)。

也就是說,我們不能通過this來獲取對象自身的屬性或方法。

那怎么辦呢?有的同學(xué)可能會選擇將自身一些公共方法,直接寫在class外部,或者寫在某個utils文件中,然后在接口方法中使用。比如這樣:

const error = require("utils/error")

const success = (ctx, data) => {
  ctx.body = {
    success: true,
    data: data
  }
}

class Question {
  async get () {
    success(data)
  }
  async create () {
    error(result)
  }
}

module.exports = new Question()

這樣確實(shí)ok,但是又會有新的問題---這些方法就不是對象自己的屬性,也就沒辦法被子類繼承了。

為什么需要繼承呢?因?yàn)橛袝r(shí)候我們希望一些不同的controller有著公共的方法或?qū)傩裕e個例子:我希望我所有的成功or失敗都是這樣的格式:

{
  success: false,
  message: "對應(yīng)的錯誤消息"
}
{
  success: true,
  data: "對應(yīng)的數(shù)據(jù)"
}

按照koa的核心思想,這個通用的格式轉(zhuǎn)化,應(yīng)該是專門編寫一個中間件,在路由中間件之后(即執(zhí)行完controller里的方法之后)去做專門處理并response。

然而這樣會導(dǎo)致每有一個公共方法,就必須要加一個中間件。而且controller本身已經(jīng)失去了對這些方法的控制權(quán)。這個中間件是執(zhí)行自身還是直接next()將會非常難判斷。

如果是抽離成utils方法再引用,也不是不可以,就是方法多的話,聲明引用稍微麻煩些,而且沒有抽象類的意義。

更理想的狀態(tài)應(yīng)該是如剛才所說的,大家都繼承一個抽象的父類,然后去調(diào)用父類的公共相應(yīng)方法即可,如:

class AbstractController {
  success (ctx, data) {
    ctx.body = {
      success: true,
      data: data
    }
  }
  error (ctx, error) {
    ctx.body = {
      success: false,
      msg: error
    }
  }
}
class Question extends AbstractController {
  async get (ctx) {
    const data = await getData(ctx.params.id)
    return super.success(ctx, data)
  }
}

這樣就方便多了,不過如果寫過koa的人可能會有這樣的煩惱,一個上下文ctx總是要作為參數(shù)傳遞來傳遞去。比如上述控制器的所有中間件方法都得傳ctx參數(shù),調(diào)用父類方法時(shí),又要傳它,還會使得方法損失一些可讀性。

所以總結(jié)一下,我們有如下問題:

controller中的方法無法調(diào)用自身的其他方法、屬性;

調(diào)用父類方法時(shí),需要傳遞上下文參數(shù)ctx

解決它

其實(shí)解決的辦法很簡單,我們只要想辦法讓controller方法中的this指向?qū)嵗瘜ο笞陨恚侔?b>ctx掛在到這個this上即可。

怎么做呢?我們只要再封裝一下koa-router就好了,如下所示:

const Router = require("koa-router")
const router = new Router()
const question = require("./controller/question")
const topic = require("./controller/topic")

const routerMap = [
  ["post", "/api/question", question, "create"],
  ["get", "/api/question", question, "get"],
  ["get", "/api/topic", topic, "get"],
  ["post", "/api/topic/follow", topic, "follow"]
]

routerMap.map(route => {
  const [ method, path, controller, action ] = route

  router[method](path, async (ctx, next) =>
    controller[action].bind(Object.assign(controller, { ctx }))(ctx, next)
  )
})

module.exports = router

大意就是在路由傳遞controller方法時(shí),將controller自身與ctx合并,通過bind指定該方法的this。這樣我們就能通過this獲取方法所屬controller對象的其他方法。此外子類方法與父類方法也能通過this.ctx來獲取上下文對象ctx

但是bind之前我們其實(shí)應(yīng)該考慮以下,其他中間件以及koa本身會不會也干了類似的事,修改了this的值。如何判斷呢,兩個辦法:

調(diào)試。在我們未bind之前,在中間件方法中打印一下this,是undefined的話自然就沒被綁定。

看koa-router/koa/node的源代碼。

事實(shí)是,自然是沒有的。那我們就放心的bind吧。

寫在最后

上述大概就是編寫這個小工具時(shí),遇到的一些點(diǎn),感覺可以總結(jié)的。也并沒有什么技術(shù)難點(diǎn),不過可以借此學(xué)習(xí)學(xué)習(xí)一些相關(guān)的知識,包括網(wǎng)站安全、爬與反爬、、koa底層原理等等。

這個工具本身非常的個人色彩,不一定滿足大家的需要。而且它在半年前就寫好了,只不過最近被我挖墳?zāi)贸鰜砜偨Y(jié)。而且就在我即將寫完文章時(shí),我發(fā)現(xiàn)知乎提示我的賬號不安全了。我估計(jì)是以為同一IP同一賬戶發(fā)起過多的網(wǎng)絡(luò)請求,我這臺服務(wù)器IP已經(jīng)被認(rèn)為是不安全的IP了,在這上面登錄的賬戶都會被提示不安全。所以我不建議大家將其直接拿來使用。

當(dāng)然,如果還是對其感興趣,本地測試下或者學(xué)習(xí)使用,還是沒什么大問題的。或者還有更深的興趣的話,可以自己嘗試去繞開知乎的安全策略。

最后的最后附上 項(xiàng)目GitHub地址

--閱讀原文

--轉(zhuǎn)載請先經(jīng)過本人授權(quán)。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/89772.html

相關(guān)文章

  • [打怪升級]小程序評論回復(fù)和發(fā)貼功能實(shí)戰(zhàn)(一)

    摘要:往期回顧打怪升級小程序評論回復(fù)和發(fā)貼功能實(shí)戰(zhàn)二填坑手冊小程序生成海報(bào)一拆彈時(shí)刻小程序生成海報(bào)二填坑手冊小程序目錄結(jié)構(gòu)和組件使用心得 showImg(https://segmentfault.com/img/remote/1460000019733090?w=818&h=516); 在學(xué)習(xí)成長的過程中,常常會遇到一些自己從未接觸的事物,這就好比是打怪升級,每次打倒一只怪,都會獲得經(jīng)驗(yàn),讓...

    YJNldm 評論0 收藏0
  • 前端修煉之路

    摘要:一步,兩步,三步四步五步,就這樣到達(dá)了人生的巔峰傳統(tǒng)前端生態(tài)初級不使用打包中間處理工具,手工處理圖片等資源掌握以下知識點(diǎn)基礎(chǔ)結(jié)構(gòu),基礎(chǔ)樣式,基礎(chǔ)語法框架,系列插件框架,等基礎(chǔ)插件,等其他移動端適配,瀏覽器兼容,瀏覽器調(diào)試等恭喜完成新手村修 一步,兩步,三步四步五步,就這樣到達(dá)了人生的巔峰~ 傳統(tǒng)前端生態(tài)-初級 不使用打包、中間處理工具,手工處理js、css、圖片等資源 掌握以下知識點(diǎn):...

    Jason_Geng 評論0 收藏0
  • 前端修煉之路

    摘要:一步,兩步,三步四步五步,就這樣到達(dá)了人生的巔峰傳統(tǒng)前端生態(tài)初級不使用打包中間處理工具,手工處理圖片等資源掌握以下知識點(diǎn)基礎(chǔ)結(jié)構(gòu),基礎(chǔ)樣式,基礎(chǔ)語法框架,系列插件框架,等基礎(chǔ)插件,等其他移動端適配,瀏覽器兼容,瀏覽器調(diào)試等恭喜完成新手村修 一步,兩步,三步四步五步,就這樣到達(dá)了人生的巔峰~ 傳統(tǒng)前端生態(tài)-初級 不使用打包、中間處理工具,手工處理js、css、圖片等資源 掌握以下知識點(diǎn):...

    macg0406 評論0 收藏0
  • [前端打怪升級--LV.1] react基礎(chǔ)內(nèi)容

    摘要:在第一次渲染后調(diào)用,只在客戶端。返回一個布爾值。在初始化時(shí)或者使用時(shí)不被調(diào)用。在組件接收到新的或者但還沒有時(shí)被調(diào)用。在組件完成更新后立即調(diào)用。在組件從中移除之前立刻被調(diào)用。 react使用教程 變量的使用 constructor(props) { super(props); this.state = { sliderSwiper: null, movies: [] }; this.ha...

    wean 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<