手撕Redux

前言

Redux 會(huì)用但又好像不知所以然读规?通過(guò)敲個(gè)todolist的例子來(lái)自己實(shí)現(xiàn)一個(gè) redux 來(lái)深刻理解它的原理吧!

完整項(xiàng)目代碼:
https://github.com/LiaPig/redux-react-todolist

一燃少、沒(méi)有 redux 的 todolist

重點(diǎn)在于 redux 邏輯束亏,所以樣式布局啥的freestyle。

這階段為止的代碼在 pure 分支阵具。

二碍遍、引入 action 概念 和 actionCreator 概念

這階段為止的代碼在 ac_dis 分支定铜。

1. 引入 action 概念

其實(shí)我們可以發(fā)現(xiàn),對(duì)于 todos 這個(gè)數(shù)據(jù)狀態(tài)來(lái)說(shuō)怕敬,修改它的操作其實(shí)就只有四個(gè):set揣炕、adddelete东跪、toggle祝沸。
但是在具體用到這三個(gè)操作的地方很多,而且很分散越庇。

那能不能使用對(duì)象的形式來(lái)描述這些會(huì)修改狀態(tài)的操作呢? type 字段來(lái)描述這是什么操作(set奉狈、add卤唉、deletetoggle)仁期, payload 字段來(lái)描述這操作會(huì)用到的具體參數(shù)桑驱。

因此,一個(gè) action 就形如:

{ 
  type: '操作名稱', 
  payload // 操作用到的參數(shù)
}

那么對(duì)應(yīng)的操作就可以改寫為:

// 頁(yè)面加載獲取 localstorage 時(shí)更新 todos
const setAction = {
  type: 'set',
  payload // 將會(huì)是一個(gè)todos數(shù)組
}

// 新增一個(gè) todo
const addAction = {
  type: 'add',
  payload // 將會(huì)是一個(gè)todo對(duì)象
}

// 刪除一個(gè) todo
const deleteAction = {
  type: 'delete',
  payload // 將會(huì)是一個(gè)id
}

// 切換某個(gè) todo 的完成狀態(tài)
const toggleAction = {
  type: 'toggle',
  payload // 將會(huì)是一個(gè)id
}

2. 引入 actionCreator 概念

那既然 action 都是個(gè)對(duì)象跛蛋,且 key 都固定為只有 typepayload 熬的。那機(jī)智的我們是不是可以寫一個(gè)函數(shù),函數(shù)接收一個(gè)參數(shù) payload 赊级,然后返回一個(gè) action 對(duì)象呢押框?—— 這就是 actionCreator

那么我們就來(lái)改寫成三個(gè) actionCreator

function createAdd(payload) {
  return {
    type: 'add',
    payload  // 將會(huì)是一個(gè)todo對(duì)象
  }
}

function createDelete(payload) {
  return {
    type: 'delete',
    payload  // 將會(huì)是一個(gè)id
  }
}

function createToggle(payload) {
  return {
    type: 'toggle',
    payload  // 將會(huì)是一個(gè)id
  }
}

三理逊、引入dispatch概念

這階段為止的代碼在 ac_dis 分支橡伞。

那么,我們可以寫一個(gè)函數(shù) dispatch晋被,讓它作為事件的中心兑徘,只有通過(guò)它才能調(diào)用對(duì)應(yīng)的操作,讓修改數(shù)據(jù)狀態(tài)的具體邏輯就只能在它里面寫羡洛,方便修改維護(hù)挂脑。

const dispatch = (action) => {
  const { type, payload } = action;

  switch (type) {
    case 'set':
      // 執(zhí)行更新todos列表的操作
      break:
    case 'add' : 
      // 執(zhí)行add一個(gè)todo的操作
      break;
    case 'delete' : 
      // 執(zhí)行delete一個(gè)todo的操作
      break;
    case 'toggle' : 
      // 執(zhí)行toggle一個(gè)todo的complete屬性的操作
      break;
    default:;
  }
}

四、引入 bindActionCreators 概念

這階段為止的代碼在 bindActionCreators 分支欲侮。

綜上所述崭闲,我們可以觀察到,要調(diào)用修改 todos 數(shù)據(jù)狀態(tài)修改的四個(gè)操作都是以下形式:

// 修改todos
dispatch(createSet(todos));
// 新增一個(gè)todo
dispatch(createAdd(todo));
// 刪除一個(gè)todo
dispatch(createAdd(id));
// 切換一個(gè)todo的complete屬性
dispatch(createToggle(id));

1. 接收參數(shù)

那么威蕉,是不是也可以寫一個(gè)函數(shù) bindActionCreators镀脂,讓它接收兩個(gè)參數(shù):

  1. 一個(gè)是用來(lái)生成createSet(todos)這一塊的(有時(shí)候可能不止需要一個(gè)呢,所以用 對(duì)象 來(lái)表示忘伞,key為自定義這個(gè)操作的名字薄翅,value為對(duì)應(yīng)的 actionCreator
  2. 另外一個(gè)是dispatch函數(shù)
 function bindActionCreators({ ‘自定義的操作名如addTodo’, createTodo }, dispatch) {
  // 這里先省略沙兰,重點(diǎn)看接收的參數(shù)與格式
}

2.函數(shù)返回值

因?yàn)榻鈽?gòu)語(yǔ)法的便利性,我們可以將返回值也定義為一個(gè) 對(duì)象 , 對(duì)象中的 key 就為函數(shù)接收第一個(gè)參數(shù)對(duì)象里的 key 翘魄,返回對(duì)象的 value 就為一個(gè)函數(shù)鼎天,之后通過(guò)調(diào)用這個(gè)函數(shù)就可以幫我們實(shí)現(xiàn) dispatch(createAdd(todo))

 function bindActionCreators({ ‘自定義的操作名如addTodo’, createAdd }, dispatch) {
  const result = {
     ‘自定義的操作名如addTodo’: function valueFunc(...args) {
        const action = createAdd(...args)
        dispatch(createAdd(action)
      }
  };
  return result;
}

3.如何調(diào)用這個(gè)函數(shù)

知道函數(shù)的輸入暑竟、輸出之后斋射,我們可以推測(cè)到,調(diào)用的格式為(還是要添加一個(gè)待辦的場(chǎng)景):

const { addTodo } = bindActionCreators({ addTodo: createAdd }, dispatch);

addTodo({ id: '~~~', text: '~~~', complete: false })

4.優(yōu)化bindActionCreators函數(shù)

就像在 List 組件里但荤,同時(shí)會(huì)用到 toggledelete 操作罗岖,就可以優(yōu)化讓 bindActionCreators 能返回多個(gè)操作:

const bindActionCreators = (actionCreators, dispatch) => {
  const result = {};
  for(let key in actionCreators) {
    result[key] = function (payload) {
      const actionCreator = actionCreators[key];
      const action = actionCreator(payload);

      dispatch(action);
    }
  }
  return result;
}

五、引入 reducer 概念 和 combineReucers 概念

這階段為止的代碼在 reducer 分支腹躁。

1. 引入 Reducer 概念

之前的例子一直就只有一個(gè) state (todos) 桑包。但項(xiàng)目中往往不可能這么簡(jiǎn)單,所以再新加一個(gè) incrementCount纺非,每新加一個(gè)todo就加一哑了,只增不減。那么就在 add 操作中會(huì)調(diào)用到烧颖。

于是我們可以發(fā)現(xiàn)到弱左,當(dāng) state 變多的時(shí)候,要根據(jù) action 操作更新 state 的步驟似乎會(huì)變得混亂炕淮。( dispatch 中根據(jù) action 即要修改 todos 的值拆火,也要修改 incrementCount 的值)。

所以我們?cè)O(shè)想有一個(gè)函數(shù) reducer涂圆,它接收兩個(gè)參數(shù):

  • 一個(gè)是 state 的值
  • 另一個(gè)是即將要發(fā)生的 action

函數(shù) reducer 的返回值就為經(jīng)過(guò)這個(gè) action 操作后 state 要改變的新的值榜掌。

// todosReducer為:
const todosReducer = (state, action) => {
  const { type, payload } = action;
 
  switch (type) {
    case 'set':
      return payload;
    case 'add':
      return [...state, payload];
    case 'delete':
      // 簡(jiǎn)潔代碼起見(jiàn),此處省略了具體返回
    case 'toggle':
      // 簡(jiǎn)潔代碼起見(jiàn)乘综,此處省略了具體返回
    default:
      return state;
  }
}

// incrementReducer為:
const incrementReducer = (state, action) => {
  const { type } = action;
 
  switch (type) {
    case 'add':
      return state + 1;
    default:
      return state;
  }
}

2. 引入 reducers 概念

我們可以發(fā)現(xiàn)憎账,當(dāng)前 todolistaction 始終只有那四個(gè)(setadd卡辰、delete胞皱、toggle)。每一個(gè) state 都應(yīng)該有一個(gè) reducer 來(lái) 根據(jù) action 做出相應(yīng)的值改變九妈。但是要為每一個(gè) state 都寫一個(gè) reducer 太麻煩了反砌,而且重復(fù)代碼非常多。

所以我們可以用一個(gè) reducers 對(duì)象萌朱,來(lái)專門描述不同 state 根據(jù)不同 action要做出的值改變宴树。可以讓 keystate 名晶疼,value 為一個(gè)函數(shù)酒贬,函數(shù)的返回值就為根據(jù)這個(gè) action 改變后的 state 的值又憨。

const reducers = {
    todos: (state, action) => {
        const { type, payload } = action;

        switch (type) {
            case 'set':
                return payload;
            case 'add':
                return [...state, payload];
            case 'delete':
                return state.filter(item => item.id !== payload)
            case 'toggle':
                const newTodo = [...state];
                const index = newTodo.findIndex(item => item.id === payload); // 把原來(lái) id 換成 payload
                newTodo[index].complete = !newTodo[index].complete;
                return newTodo;
            default:
                return state;
        }
    },
    incrementCount: (state, action) => {
        const { type } = action;

        switch (type) {
            case 'add':
                return state + 1;
            default:
                return state;
        }
    }
};

3. 引入 combineReducer 概念

dispatch 函數(shù)的作用是根據(jù) action 來(lái)改變 state 的值。那么有了 reducer 后锭吨,原本在 dispatch 函數(shù)里的 state 具體如何發(fā)生變化已經(jīng)不再需要 dispatch 函數(shù)去關(guān)注了蠢莺,我們可以調(diào)用 reducer 函數(shù),獲取到它返回的新 state 值零如。而dispatch 函數(shù)只需要觸發(fā)更新就行了躏将。

我們假設(shè)通過(guò) reducer 能獲取到根據(jù)這個(gè) action 操作后所有修改后的 state 值的集合,一個(gè)大對(duì)象 states考蕾。( keystate 的名字祸憋,value 為新的值)然后遍歷這個(gè) states,去為每一項(xiàng) state 都去執(zhí)行它的 setter 函數(shù)肖卧,從而去更新值(無(wú)論值有沒(méi)改變蚯窥,都去調(diào)用 setteruseState 有做這個(gè)的性能優(yōu)化喜命,所以不用擔(dān)心性能)

于是,我們先嘗試修改 dispatch 函數(shù)的代碼:

const dispatch = useCallback((action) => {
  // 將所有的state茬祷,封裝在一個(gè)大的 states 對(duì)象里奖地,key萌丈、value都為state
  const states = {
    todos,
    incrementCount
  };
  // 將所有 state 的 setter,封裝在一個(gè)大的 setters 對(duì)象里牌里,key 名為 state 的名,value 為對(duì)應(yīng)的 setter
  const setters = {
     todos: setTodos,
     incrementCount: setIncrementCount
  };
  // 根據(jù)傳入的 action务甥,去調(diào)用 reducer 函數(shù)牡辽,獲取到返回的修改后的 states 值
  const newStates = reducer(states, action);
  // 循環(huán) states 大對(duì)象,更新里面的state
  for (let key in newStates) {
    setters[key](newStates[key]);
  }
}, [todos, incrementCount])

這個(gè)時(shí)候敞临,我們發(fā)現(xiàn)态辛,之前我們寫的 reducers 并不符合 dispatch 函數(shù)想要的格式呀。于是挺尿,我們需要一個(gè)轉(zhuǎn)換函數(shù) combineReducers (作用是將 reducers 轉(zhuǎn)換為 dispatch 想要的 reducer) 奏黑,它接收一個(gè)參數(shù) reducers ,返回值就為我們 dispatch 函數(shù)里想要的 reducer 格式 —— 是一個(gè)函數(shù)编矾,第一個(gè)參數(shù)為所有 state 的集合 states 熟史,第二個(gè)參數(shù)為 action ;返回值為更新的所有 newState 的集合 newStates窄俏。

// 創(chuàng)建一個(gè) combineReducers 函數(shù)蹂匹,讓它能返回 reducer 函數(shù)
const combineReducers = (reducers) => {
    // 為了更形象的表示,沒(méi)有使用箭頭函數(shù)
    return function reducer(states, action) {
        // 經(jīng)過(guò)這個(gè) action 操作凹蜈,包含了所有改變了的 state 值的 states 對(duì)象
        const changedStates = {};

        // reducers 的 key 都為 state 名
        for (let key in reducers) {
            changedStates[key] = reducers[key](states[key], action);
        }

        // 別忘了 reducer 的返回值是一個(gè)經(jīng)過(guò) action 處理后的 states 值
        return {
            ...states,
            ...changedStates
        }
    }
};

最后我們發(fā)現(xiàn)限寞,reducerscombineReducers 應(yīng)該是獨(dú)立的忍啸,我們?cè)?todolist 中,想要的就只有通過(guò) combineReducers(reducers) 轉(zhuǎn)換后的 reducer 昆烁。所以可以將 reducerscombineReducers 放在一個(gè)名為 reducers.js 的文件中吊骤,最后導(dǎo)出 combineReducers(reducers) ,在 todolist 中引入這個(gè)文件即可静尼。

// reducers.js
const reducers = { // 此處省略具體白粉,詳情可看項(xiàng)目代碼 };
const combineReducers = (reducers) => { // 此處省略具體,詳情可看項(xiàng)目代碼 };

export default combineReducers(reducers);
// App.jsx
import reducer = './reducers.js'

六鼠渺、引入異步 Action 概念

這階段為止的代碼在 reducer 分支鸭巴。

之前都是同步的操作,那如果在異步的場(chǎng)景拦盹,如何拿到正確的 state 呢鹃祖?

先來(lái)模擬一下異步的場(chǎng)景,看看 state 是否是實(shí)時(shí)的普舆。假設(shè)要新增一條 todo 不是同步的恬口,而是異步的(使用定時(shí)器來(lái)模擬)。然后還要判斷判斷現(xiàn)有的 todos 里有沒(méi) text 相同的沼侣,如果沒(méi)有祖能,才新增:

  1. 首先,之前的整個(gè)要新增的 todo 數(shù)據(jù)蛾洛,都在 Control 組件里生成的养铸,我們需要修改一下,只提供 text 字段就可以了:
addTodo({
  text: newText,
});
  1. 將新增 todoactionCreator(即 CreateAdd )改為異步 action 轧膘,使用定時(shí)器钞螟,然后在回調(diào)函數(shù)里加入判斷 text,沒(méi)有相同的才 dispatch 這個(gè) action谎碍。所以可以把 CreateAdd 的返回改為 函數(shù)鳞滨,接收兩個(gè)參數(shù),一個(gè)是dispatch蟆淀,一個(gè)是所有 statestates太援,返回 dispatch(action)
export const createAdd = (payload) => {
    // return {
    //     type: 'add',
    //     payload
    // }
    return (dispatch, getStates) => {
        setTimeout(() => {
            const { todos } = getStates()
            if (todos.findIndex(item => item.text === payload.text) === -1) {
                dispatch({
                    type: 'add',
                    payload: {
                        id: Date.now(),
                        text: payload.text,
                        complete: false
                    }
                })
            }
        }, 5000)
    }
}
  1. 接下來(lái)再修改 dispatch 函數(shù),在它調(diào)用 reducer 函數(shù)邏輯之前扳碍,先判斷 action 的類型提岔,如果 action 的類型是函數(shù)(異步 Action ),直接調(diào)用它且把 dispatchstates 傳給它笋敞,最后一定要記得 return碱蒙,不再執(zhí)行之后的操作。
if (typeof action === 'function') {
  action(dispatch, states)
  return
}
  1. 接下來(lái)實(shí)操一下,在 todos 里只有一條 text 為 "aaa" 的場(chǎng)景下赛惩,然后再新增一條 text"aaa"todo 哀墓,在敲下回車后,立馬又把原來(lái)的 todo 刪掉喷兼。

按道理來(lái)說(shuō)篮绰,我們想要的結(jié)果是五秒后 todos 有一條 "aaa"todo
然而季惯,五秒過(guò)去了吠各,todos 空空如也。

  1. 查閱代碼發(fā)現(xiàn)勉抓,原來(lái)在五秒后的回調(diào)函數(shù)里贾漏,我們拿到的 states ,是五秒前就已經(jīng)傳進(jìn)來(lái)的 states 藕筋。那時(shí)候纵散, todos 里有一條 text"aaa"todo 。所以在回調(diào)函數(shù)的判斷里隐圾,以為已經(jīng)有了伍掀,所以不會(huì)新增。那么我們嘗試把參數(shù) states 改為 getStates 函數(shù)暇藏,然后在回調(diào)里再調(diào)用再獲取 states蜜笤。

修改 createAdd 這個(gè)異步的 actionCreator 里的代碼:

export const createAdd = (payload) => {
    // 參數(shù)該為getStates
    return (dispatch, getStates) => {
        setTimeout(() => {
           // 五秒后再去獲取states里的todo
            const { todos } = getStates()
            if (todos.findIndex(item => item.text === payload.text) === -1) {
                dispatch({
                    type: 'add',
                    payload: {
                        id: Date.now(),
                        text: payload.text,
                        complete: false
                    }
                })
            }
        }, 5000)
        setTimeout(() => {
            console.log('已經(jīng)五秒啦')
        }, 5000)
    }
}

配合修改 dispatch 函數(shù)關(guān)于異步 Action 傳參的代碼:

if (typeof action === 'function') {
  action(dispatch, () => states);
  return;
}
  1. 再做跟步驟4一樣的實(shí)操

結(jié)果發(fā)現(xiàn),五秒過(guò)去叨咖,todos 還是空空如也瘩例。

再查閱代碼發(fā)現(xiàn)啊胶,dispatch 函數(shù)里的 states 對(duì)象甸各,總是在異步 Action 發(fā)起之前臨時(shí)聲明構(gòu)成的。五秒鐘之后焰坪,原數(shù)據(jù)的 states趣倾,由于中途我們刪了一個(gè)todo,所以 states 已經(jīng)發(fā)生變化某饰,但通過(guò) getStates() 獲取到的 dispatch 里的 states儒恋,還是舊的。

七黔漂、引入 store 概念

針對(duì)上面的問(wèn)題诫尽,可以猜想到,只要是在組件上下文的 states 炬守,可能都獲取不到最新值牧嫉,很有可能每次的渲染周期,返回的 states 的值都不一樣。所以酣藻,應(yīng)該把 states 都放在 App組件 之外曹洽,通過(guò) useEffect 來(lái)同步更新。

  1. 聲明提個(gè) store 對(duì)象辽剧,用來(lái)存放 states
const store = {
    todos,
    incrementCount
}
  1. useEffect 來(lái)同步更新
useEffect(() => {
  Object.assign(store, {
    todos,
    incrementCount
  })
}, [todos, incrementCount])
  1. 更換 dispatch 函數(shù)里的getStates里的返回值送淆,改為store
if (typeof action === 'function') {
  action(dispatch, () => store);
  return;
}
  1. 實(shí)操,新增一條 "aaa" 的同時(shí)怕轿,刪除原來(lái)的 "aaa" 偷崩。結(jié)果發(fā)現(xiàn),五秒后撤卢,新增了兩條 "aaa" 环凿。

能成功新增說(shuō)明異步后拿到的 todos 是最新的。

觀察代碼發(fā)現(xiàn)放吩,五秒后在回調(diào)函數(shù)里我們 dispatch 了新的 todo智听,然后是走dispatch 的同步 action 的邏輯,通過(guò) reducer 獲取到返回的修改后的 states 值渡紫。而我們?cè)谶@里傳給 reducer 的仍是舊的 states 值到推,所以把傳給 reducer 的也改為 store

然后我們還可以發(fā)現(xiàn)惕澎,在 dispatch 函數(shù)里莉测,已經(jīng)沒(méi)有使用 states 了,所以可以把參數(shù)定義去掉唧喉,還有這個(gè)函數(shù)已經(jīng)不再依賴 statestodos
incrementCount 了捣卤。所以也不再需要使用 useCallback 來(lái)包裹 dispatch 函數(shù)了。

5.再實(shí)操八孝,終于成功啦6!干跛!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末子姜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子楼入,更是在濱河造成了極大的恐慌哥捕,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘉熊,死亡現(xiàn)場(chǎng)離奇詭異遥赚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)阐肤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門凫佛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事御蒲∫赂希” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵厚满,是天一觀的道長(zhǎng)府瞄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碘箍,這世上最難降的妖魔是什么遵馆? 我笑而不...
    開(kāi)封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮丰榴,結(jié)果婚禮上货邓,老公的妹妹穿的比我還像新娘。我一直安慰自己四濒,他們只是感情好换况,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著盗蟆,像睡著了一般戈二。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喳资,一...
    開(kāi)封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天觉吭,我揣著相機(jī)與錄音,去河邊找鬼仆邓。 笑死鲜滩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的节值。 我是一名探鬼主播徙硅,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼察署!你這毒婦竟也來(lái)了闷游?” 一聲冷哼從身側(cè)響起峻汉,我...
    開(kāi)封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贴汪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后休吠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扳埂,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瘤礁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阳懂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖岩调,靈堂內(nèi)的尸體忽然破棺而出巷燥,到底是詐尸還是另有隱情,我是刑警寧澤号枕,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布缰揪,位于F島的核電站,受9級(jí)特大地震影響葱淳,放射性物質(zhì)發(fā)生泄漏钝腺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一赞厕、第九天 我趴在偏房一處隱蔽的房頂上張望艳狐。 院中可真熱鬧,春花似錦皿桑、人聲如沸毫目。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒜茴。三九已至,卻和暖如春浆西,著一層夾襖步出監(jiān)牢的瞬間粉私,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工近零, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诺核,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓久信,卻偏偏與公主長(zhǎng)得像窖杀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裙士,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349