主頁 > 企業開發 > Redux異步解決方案之Redux-Thunk原理及原始碼決議

Redux異步解決方案之Redux-Thunk原理及原始碼決議

2020-09-10 05:03:29 企業開發

前段時間,我們寫了一篇Redux原始碼分析的文章,也分析了跟React連接的庫React-Redux的原始碼實作,但是在Redux的生態中還有一個很重要的部分沒有涉及到,那就是Redux的異步解決方案,本文會講解Redux官方實作的異步解決方案----Redux-Thunk,我們還是會從基本的用法入手,再到原理決議,然后自己手寫一個Redux-Thunk來替換它,也就是原始碼決議,

Redux-Thunk和前面寫過的ReduxReact-Redux其實都是Redux官方團隊的作品,他們的側重點各有不同:

Redux:是核心庫,功能簡單,只是一個單純的狀態機,但是蘊含的思想不簡單,是傳說中的“百行代碼,千行檔案”,

React-Redux:是跟React的連接庫,當Redux狀態更新的時候通知React更新組件,

Redux-Thunk:提供Redux的異步解決方案,彌補Redux功能的不足,

本文手寫代碼已經上傳GitHub,大家可以拿下來玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/blob/master/Examples/React/redux-thunk/src/myThunk.js

基本用法

還是以我們之前的那個計數器作為例子,為了讓計數器+1,我們會發出一個action,像這樣:

function increment() {
  return {
    type: 'INCREMENT'
  }
};

store.dispatch(increment());

原始的Redux里面,action creator必須回傳plain object,而且必須是同步的,但是我們的應用里面經常會有定時器,網路請求等等異步操作,使用Redux-Thunk就可以發出異步的action

function increment() {
  return {
    type: 'INCREMENT'
  }
};

// 異步action creator
function incrementAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  }
}

// 使用了Redux-Thunk后dispatch不僅僅可以發出plain object,還可以發出這個異步的函式
store.dispatch(incrementAsync());

下面再來看個更實際點的例子,也是官方檔案中的例子:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// createStore的時候傳入thunk中間件
const store = createStore(rootReducer, applyMiddleware(thunk));

// 發起網路請求的方法
function fetchSecretSauce() {
  return fetch('https://www.baidu.com/s?wd=Secret%20Sauce');
}

// 下面兩個是普通的action
function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce,
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error,
  };
}

// 這是一個異步action,先請求網路,成功就makeASandwich,失敗就apologize
function makeASandwichWithSecretSauce(forPerson) {
  return function (dispatch) {
    return fetchSecretSauce().then(
      (sauce) => dispatch(makeASandwich(forPerson, sauce)),
      (error) => dispatch(apologize('The Sandwich Shop', forPerson, error)),
    );
  };
}

// 最終dispatch的是異步action makeASandwichWithSecretSauce
store.dispatch(makeASandwichWithSecretSauce('Me'));

為什么要用Redux-Thunk

在繼續深入原始碼前,我們先來思考一個問題,為什么我們要用Redux-Thunk,不用它行不行?再仔細看看Redux-Thunk的作用:

// 異步action creator
function incrementAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  }
}

store.dispatch(incrementAsync());

他僅僅是讓dispath多支持了一種型別,就是函式型別,在使用Redux-Thunk前我們dispatchaction必須是一個純物件(plain object),使用了Redux-Thunk后,dispatch可以支持函式,這個函式會傳入dispatch本身作為引數,但是其實我們不使用Redux-Thunk也可以達到同樣的效果,比如上面代碼我完全可以不要外層的incrementAsync,直接這樣寫:

setTimeout(() => {
  store.dispatch(increment());
}, 1000);

這樣寫同樣可以在1秒后發出增加的action,而且代碼還更簡單,那我們為什么還要用Redux-Thunk呢,他存在的意義是什么呢?stackoverflow對這個問題有一個很好的回答,而且是官方推薦的解釋,我再寫一遍也不會比他寫得更好,所以我就直接翻譯了:

----翻譯從這里開始----

不要覺得一個庫就應該規定了所有事情!如果你想用JS處理一個延時任務,直接用setTimeout就好了,即使你使用了Redux也沒啥區別,Redux確實提供了另一種處理異步任務的機制,但是你應該用它來解決你很多重復代碼的問題,如果你沒有太多重復代碼,使用語言原生方案其實是最簡單的方案,

直接寫異步代碼

到目前為止這是最簡單的方案,Redux也不需要特殊的配置:

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

(譯注:這段代碼的功能是顯示一個通知,5秒后自動消失,也就是我們經常使用的toast效果,原作者一直以這個為例,)

相似的,如果你是在一個連接了Redux組件中使用:

this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

唯一的區別就是連接組件一般不需要直接使用store,而是將dispatch或者action creator作為props注入,這兩種方式對我們都沒區別,

如果你不想寫重復的action名字,你可以將這兩個action抽取成action creator而不是直接dispatch一個物件:

// actions.js
export function showNotification(text) {
  return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
  return { type: 'HIDE_NOTIFICATION' }
}

// component.js
import { showNotification, hideNotification } from '../actions'

this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
  this.props.dispatch(hideNotification())
}, 5000)

或者你已經通過connect()注入了這兩個action creator

this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

到目前為止,我們沒有使用任何中間件或者其他高級技巧,但是我們同樣實作了異步任務的處理,

提取異步的Action Creator

使用上面的方式在簡單場景下可以作業的很好,但是你可能已經發現了幾個問題:

  1. 每次你想顯示toast的時候,你都得把這一大段代碼抄過來抄過去,
  2. 現在的toast沒有id,這可能會導致一種競爭的情況:如果你連續快速的顯示兩次toast,當第一次的結束時,他會dispatchHIDE_NOTIFICATION,這會錯誤的導致第二個也被關掉,

為了解決這兩個問題,你可能需要將toast的邏輯抽取出來作為一個方法,大概長這樣:

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  // 給通知分配一個ID可以讓reducer忽略非當前通知的HIDE_NOTIFICATION
  // 而且我們把計時器的ID記錄下來以便于后面用clearTimeout()清除計時器
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

現在你的組件可以直接使用showNotificationWithTimeout,再也不用抄來抄去了,也不用擔心競爭問題了:

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')  

但是為什么showNotificationWithTimeout()要接收dispatch作為第一個引數呢?因為他需要將action發給store,一般組件是可以拿到dispatch的,為了讓外部方法也能dispatch,我們需要給他dispath作為引數,

如果你有一個單例的store,你也可以讓showNotificationWithTimeout直接引入這個store然后dispatch action

// store.js
export default createStore(reducer)

// actions.js
import store from './store'

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  const id = nextNotificationId++
  store.dispatch(showNotification(id, text))

  setTimeout(() => {
    store.dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout('You just logged in.')

// otherComponent.js
showNotificationWithTimeout('You just logged out.') 

這樣做看起來不復雜,也能達到效果,但是我們不推薦這種做法!主要原因是你的store必須是單例的,這讓Server Render實作起來很麻煩,在Server端,你會希望每個請求都有自己的store,比便于不同的用戶可以拿到不同的預加載內容,

一個單例的store也讓單元測驗很難寫,測驗action creator的時候你很難mock store,因為他參考了一個具體的真實的store,你甚至不能從外部重置store狀態,

所以從技術上來說,你可以從一個module匯出單例的store,但是我們不鼓勵這樣做,除非你確定加肯定你以后都不會升級Server Render,所以我們還是回到前面一種方案吧:

// actions.js

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')  

這個方案就可以解決重復代碼和競爭問題,

Thunk中間件

對于簡單專案,上面的方案應該已經可以滿足需求了,

但是對于大型專案,你可能還是會覺得這樣使用并不方便,

比如,似乎我們必須將dispatch作為引數傳遞,這讓我們分隔容器組件和展示組件變得更困難,因為任何發出異步Redux action的組件都必須接收dispatch作為引數,這樣他才能將它繼續往下傳,你也不能僅僅使用connect()來系結action creator,因為showNotificationWithTimeout()并不是一個真正的action creator,他回傳的也不是Redux action

還有個很尷尬的事情是,你必須記住哪個action cerator是同步的,比如showNotification,哪個是異步的輔助方法,比如showNotificationWithTimeout,這兩個的用法是不一樣的,你需要小心的不要傳錯了引數,也不要混淆了他們,

這就是我們為什么需要找到一個“合法”的方法給輔助方法提供dispatch引數,并且幫助Redux區分出哪些是異步的action creator,好特殊處理他們

如果你的專案中面臨著類似的問題,歡迎使用Redux Thunk中間件,

簡單來說,React Thunk告訴Redux怎么去區分這種特殊的action----他其實是個函式:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

// 這個是普通的純物件action
store.dispatch({ type: 'INCREMENT' })

// 但是有了Thunk,他就可以識別函式了
store.dispatch(function (dispatch) {
  // 這個函式里面又可以dispatch很多action
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })

  setTimeout(() => {
    // 異步的dispatch也可以
    dispatch({ type: 'DECREMENT' })
  }, 1000)
})

如果你使用了這個中間件,而且你dispatch的是一個函式,React Thunk會自己將dispatch作為引數傳進去,而且他會將這些函式action“吃了”,所以不用擔心你的reducer會接收到奇怪的函式引數,你的reducer只會接收到純物件action,無論是直接發出的還是前面那些異步函式發出的,

這個看起來好像也沒啥大用,對不對?在當前這個例子確實是的!但是他讓我們可以像定義一個普通的action creator那樣去定義showNotificationWithTimeout

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

注意這里的showNotificationWithTimeout跟我們前面的那個看起來非常像,但是他并不需要接收dispatch作為第一個引數,而是回傳一個函式來接收dispatch作為第一個引數,

那在我們的組件中怎么使用這個函式呢,我們當然可以這樣寫:

// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)

這樣我們直接呼叫了異步的action creator來得到內層的函式,這個函式需要dispatch做為引數,所以我們給了他dispatch引數,

然而這樣使用豈不是更尬,還不如我們之前那個版本的!我們為啥要這么干呢?

我之前就告訴過你:只要使用了Redux Thunk,如果你想dispatch一個函式,而不是一個純物件,這個中間件會自己幫你呼叫這個函式,而且會將dispatch作為第一個引數傳進去,

所以我們可以直接這樣干:

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

最后,對于組件來說,dispatch一個異步的action(其實是一堆普通action)看起來和dispatch一個普通的同步action看起來并沒有啥區別,這是個好現象,因為組件就不應該關心那些動作到底是同步的還是異步的,我們已經將它抽象出來了,

注意因為我們已經教了Redux怎么區分這些特殊的action creator(我們稱之為thunk action creator),現在我們可以在任何普通的action creator的地方使用他們了,比如,我們可以直接在connect()中使用他們:

// actions.js

function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

在Thunk中讀取State

通常來說,你的reducer會包含計算新的state的邏輯,但是reducer只有當你dispatchaction才會觸發,如果你在thunk action creator中有一個副作用(比如一個API呼叫),某些情況下,你不想發出這個action該怎么辦呢?

如果沒有Thunk中間件,你需要在組件中添加這個邏輯:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

但是我們提取action creator的目的就是為了集中這些在各個組件中重復的邏輯,幸運的是,Redux Thunk提供了一個讀取當前store state的方法,那就是除了傳入dispatch引數外,他還會傳入getState作為第二個引數,這樣thunk就可以讀取store的當前狀態了,

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch, getState) {
    // 不像普通的action cerator,這里我們可以提前退出
    // Redux不關心這里的回傳值,沒回傳值也沒關系
    if (!getState().areNotificationsEnabled) {
      return
    }

    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

但是不要濫用這種方法!如果你需要通過檢查快取來判斷是否發起API請求,這種方法就很好,但是將你整個APP的邏輯都構建在這個基礎上并不是很好,如果你只是用getState來做條件判斷是否要dispatch action,你可以考慮將這些邏輯放到reducer里面去,

下一步

現在你應該對thunk的作業原理有了一個基本的概念,如果你需要更多的例子,可以看這里:https://redux.js.org/introduction/examples#async,

你可能會發現很多例子都回傳了Promise,這個不是必須的,但是用起來卻很方便,Redux并不關心你的thunk回傳了什么值,但是他會將這個值通過外層的dispatch()回傳給你,這就是為什么你可以在thunk中回傳一個Promise并且等他完成:

dispatch(someThunkReturningPromise()).then(...)

另外你還可以將一個復雜的thunk action creator拆分成幾個更小的thunk action creator,這是因為thunk提供的dispatch也可以接收thunk,所以你可以一直嵌套的dispatch thunk,而且結合Promise的話可以更好的控制異步流程,

在一些更復雜的應用中,你可能會發現你的異步控制流程通過thunk很難表達,比如,重試失敗的請求,使用token進行重新授權認證,或者在一步一步的引導流程中,使用這種方式可能會很繁瑣,而且容易出錯,如果你有這些需求,你可以考慮下一些更高級的異步流程控制庫,比如Redux Saga或者Redux Loop,可以看看他們,評估下,哪個更適合你的需求,選一個你最喜歡的,

最后,不要使用任何庫(包括thunk)如果你沒有真實的需求,記住,我們的實作都是要看需求的,也許你的需求這個簡單的方案就能滿足:

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

不要跟風嘗試,除非你知道你為什么需要這個!

----翻譯到此結束----

StackOverflow的大神Dan Abramov對這個問題的回答實在太細致,太到位了,以致于我看了之后都不敢再寫這個原因了,以此翻譯向大神致敬,再貼下這個回答的地址:https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559,

PS: Dan Abramov是Redux生態的核心作者,這幾篇文章講的ReduxReact-ReduxRedux-Thunk都是他的作品,

原始碼決議

上面關于原因的翻譯其實已經將Redux適用的場景和原理講的很清楚了,下面我們來看看他的原始碼,自己仿寫一個來替換他,照例我們先來分析下要點:

  1. Redux-Thunk是一個Redux中間件,所以他遵守Redux中間件的范式,
  2. thunk是一個可以dispatch的函式,所以我們需要改寫dispatch讓他接受函式引數,

Redux中間件范式

在我前面那篇講Redux原始碼的文章講過中間件的范式以及Redux中這塊原始碼是怎么實作的,沒看過或者忘了的朋友可以再去看看,我這里再簡單提一下,一個Redux中間件結構大概是這樣:

function logger(store) {
  return function(next) {
    return function(action) {
      console.group(action.type);
      console.info('dispatching', action);
      let result = next(action);
      console.log('next state', store.getState());
      console.groupEnd();
      return result
    }
  }
}

這里注意幾個要點:

  1. 一個中間件接收store作為引數,會回傳一個函式
  2. 回傳的這個函式接收老的dispatch函式作為引數(也就是代碼中的next),會回傳一個新的函式
  3. 回傳的新函式就是新的dispatch函式,這個函式里面可以拿到外面兩層傳進來的store和老dispatch函式

仿照這個范式,我們來寫一下thunk中間件的結構:

function thunk(store) {
  return function (next) {
    return function (action) {
      // 先直接回傳原始結果
      let result = next(action);
      return result
    }
  }
}

處理thunk

根據我們前面講的,thunk是一個函式,接收dispatch getState兩個引數,所以我們應該將thunk拿出來運行,然后給他傳入這兩個引數,再將它的回傳值直接回傳就行,

function thunk(store) {
  return function (next) {
    return function (action) {
      // 從store中解構出dispatch, getState
      const { dispatch, getState } = store;

      // 如果action是函式,將它拿出來運行,引數就是dispatch和getState
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      // 否則按照普通action處理
      let result = next(action);
      return result
    }
  }
}

接收額外引數withExtraArgument

Redux-Thunk還提供了一個API,就是你在使用applyMiddleware引入的時候,可以使用withExtraArgument注入幾個自定義的引數,比如這樣:

const api = "http://www.example.com/sandwiches/";
const whatever = 42;

const store = createStore(
  reducer,
  applyMiddleware(thunk.withExtraArgument({ api, whatever })),
);

function fetchUser(id) {
  return (dispatch, getState, { api, whatever }) => {
    // 現在你可以使用這個額外的引數api和whatever了
  };
}

這個功能要實作起來也很簡單,在前面的thunk函式外面再包一層就行:

// 外面再包一層函式createThunkMiddleware接收額外的引數
function createThunkMiddleware(extraArgument) {
  return function thunk(store) {
    return function (next) {
      return function (action) {
        const { dispatch, getState } = store;

        if (typeof action === 'function') {
          // 這里執行函式時,傳入extraArgument
          return action(dispatch, getState, extraArgument);  
        }

        let result = next(action);
        return result
      }
    }
  }
}

然后我們的thunk中間件其實相當于沒傳extraArgument

const thunk = createThunkMiddleware();

而暴露給外面的withExtraArgument函式就直接是createThunkMiddleware了:

thunk.withExtraArgument = createThunkMiddleware;

原始碼決議到此結束,啥,這就完了?是的,這就完了!Redux-Thunk就是這么簡單,雖然背后的思想比較復雜,但是代碼真的只有14行!我當時也震驚了,來看看官方原始碼吧:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

總結

  1. 如果說Redux是“百行代碼,千行檔案”,那Redux-Thunk就是“十行代碼,百行思想”,
  2. Redux-Thunk最主要的作用是幫你給異步action傳入dispatch,這樣你就不用從呼叫的地方手動傳入dispatch,從而實作了呼叫的地方和使用的地方的解耦,
  3. ReduxRedux-Thunk讓我深深體會到什么叫“編程思想”,編程思想可以很復雜,但是實作可能并不復雜,但是卻非常有用,
  4. 在我們評估是否要引入一個庫時最好想清楚我們為什么要引入這個庫,是否有更簡單的方案,

本文手寫代碼已經上傳GitHub,大家可以拿下來玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/blob/master/Examples/React/redux-thunk/src/myThunk.js

參考資料

Redux-Thunk檔案:https://github.com/reduxjs/redux-thunk

Redux-Thunk原始碼: https://github.com/reduxjs/redux-thunk/blob/master/src/index.js

Dan Abramov在StackOverflow上的回答: https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559

文章的最后,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支持是作者持續創作的動力,

作者博文GitHub專案地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者掘金文章匯總:https://juejin.im/post/5e3ffc85518825494e2772fd

我也搞了個公眾號[進擊的大前端],不打廣告,不寫水文,只發高質量原創,歡迎關注~

QRCode

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/945.html

標籤:JavaScript

上一篇:蒲公英 · JELLY技術周刊 Vol.21 -- 技術周刊 · React Hooks vs Vue 3 + Composition API

下一篇:ES6特性整理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more