React技術(shù)棧耕耘 —— Redux

Redux 是近年來提出的 Flux 思想的一種實踐方案芦缰,在它之前也有 reflux 蓄坏、 fluxxor 等高質(zhì)量的作品价捧,但短短幾個月就在 GitHub 上獲近萬 star 的成績讓這個后起之秀逐漸成為 Flux 的主流實踐方案。

正如 Redux 官方所稱涡戳,React 禁止在視圖層直接操作 DOM 和異步行為 ( removing both asynchrony and direct DOM manipulation )结蟋,來拆開異步和變化這一對冤家。但它依然把狀態(tài)的管理交到了我們手中渔彰。Redux 就是我們的狀態(tài)管理小管家嵌屎。

安利的話先暫時說到這,本次我們聊聊 React-Redux 在滬江前端團隊中的實踐恍涂。

0. 放棄

你沒有看錯宝惰,在開始之前我們首先談論一下什么情況下不應該用 Redux。

所謂殺雞焉用宰牛刀再沧,任何技術(shù)方案都有其適用場景尼夺。作為一個思想的實踐方案,Redux 必然會為實現(xiàn)思想立規(guī)矩、鋪基礎汞斧,放在復雜的 React 應用里夜郁,它會是“金科玉律”,而放在結(jié)構(gòu)不算復雜的應用中粘勒,它只會是“繁文縟節(jié)”竞端。

如果我們將要構(gòu)建的應用無需多層組件嵌套,狀態(tài)變化簡單庙睡,數(shù)據(jù)單一事富,那么就應放棄 Redux ,選用單純的 React 庫 或其他 MV* 庫乘陪。畢竟统台,沒有人愿意雇傭一個收費比自己收入還高的財務顧問。

1. 思路

首先啡邑,我們回顧一下 Redux 的基本思路

redux flow

當用戶與界面交互時贱勃,交互事件的回調(diào)函數(shù)會觸發(fā) ActionCreators ,它是一個函數(shù)谤逼,返回一個對象贵扰,該對象攜帶了用戶的動作類型和修改 Model 必需的數(shù)據(jù),這個對象也被我們稱作 Action 流部。

以 TodoList 為例戚绕,添加一個 Todo 項的 ActionCreator 函數(shù)如下所示(如果不熟悉 ES6 箭頭函數(shù)請移步這里):

const addTodo = text => ({
    type: 'ADD_TODO',
    text
});

在上例中,addTodo 就是 ActionCreator 函數(shù)枝冀,該函數(shù)返回的對象就是 Action 舞丛。

其中 type 為 Redux 中約定的必填屬性,它的作用稍后我們會講到果漾。而 text 則是執(zhí)行 “添加 Todo 項“ 這個動作必需的數(shù)據(jù)球切。

當然,不同動作所需要的數(shù)據(jù)也不盡相同绒障,如 “刪除Todo” 動作欧聘,我們就需要知道 todo 項的 id,“拉取已有的Todo項” 動作端盆,我們就需要傳入一個數(shù)組( todos )怀骤。形如 text 、 id 焕妙、 todos 這類屬性蒋伦,我們習慣稱呼其為 “ payload ” 。

現(xiàn)在焚鹊,我們得到了一個 “栩栩如生” 的動作痕届。它足夠簡潔韧献,但擔任 Model 的 store 暫時還不知道如何感知這個動作從而改變數(shù)據(jù)結(jié)構(gòu)。

為了處理這個關(guān)鍵問題研叫,Reducer 巧然登場锤窑。它仍然是一個函數(shù),而且是沒有副作用的純函數(shù)嚷炉。它只接收兩個參數(shù):state 和 action 渊啰,返回一個 newState 。

沒錯申屹,state 就是你在 React 中熟知的 state绘证,但根據(jù) Redux 三原則 之一的 “單一數(shù)據(jù)源” 原則,Reducer 幽幽地說:“你的 state 被我承包了哗讥∪履牵”

于是,單一數(shù)據(jù)源規(guī)則實施起來杆煞,是規(guī)定用 React 的頂層容器組件( Container Components )的 state 來存儲單一對象樹魏宽,同時交給 Redux store 來管理。

這里區(qū)分一下 state 和 Redux store:state 是真正儲存數(shù)據(jù)的對象樹决乎,而 Redux store 是協(xié)調(diào) Reducer湖员、state、Action 三者的調(diào)度中心瑞驱。

而如此前所說,Reducer 此時手握兩個關(guān)鍵信息:舊的數(shù)據(jù)結(jié)構(gòu)(state)窄坦,還有改變它所需要的信息 (action)唤反,然后聰明的 Reducer 算盤一敲,就能給出一個新的 state 鸭津,從而更新數(shù)據(jù)彤侍,響應用戶。下面依然拿 TodoList
舉例(不熟悉 “...” ES6 rest/spread 語法請先看這里):

//整個 todoList 最原始的數(shù)據(jù)結(jié)構(gòu)逆趋。
const initState = {
    filter: 'ALL',
    todos: []
};
//Reducer 識別動作類型為 ADD_TODO 時的處理函數(shù)
const handleAddTodo = (state, text) => {
    const todos = state.todos;
    const newState = {...state, {
        todos: [
            ...todos, {
            text,
            completed: false
        }]
    }};
    return newState;
};
//Reducer 函數(shù)
const todoList = (state = initState, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return handleAddTodo(state, action.text);
        default:
            return state;
    }
}

當接收到一個 action 時盏阶,Reducer 從 action.type 識別出該動作是要添加 Todo 項,然后路由到相應的處理方案闻书,接著根據(jù) action.text 完成了處理名斟,返回一個 newState 。過程之間魄眉,整個應用的 state 就從 state => newState 完成了狀態(tài)的變更砰盐。

這個過程讓我們很自然地聯(lián)想到去銀行存取錢的經(jīng)歷,顯然我們應該告訴柜臺操作員要存取錢坑律,而不是遙望著銀行的金庫自言自語岩梳。

Reducer 為我們梳理了所有變更 state 的方式,那么 Redux store 從無到有,從有到變都應該與 Reducer 強關(guān)聯(lián)冀值。

因此也物,Redux 提供了 createStore 函數(shù),他的第一個參數(shù)就是 Reducer 列疗,用以描繪 state 的更改方式滑蚯。第二個是可選參數(shù) initialState ,此前我們知道作彤,這個 initialState 參數(shù)也可以傳給 Reducer 函數(shù)膘魄。放在這里做可選參數(shù)的原因是為同構(gòu)應用提供便捷。

//store.js
import reducer from './reducer';
import { createStore } from 'redux';
export default createStore(reducer);

createStore 函數(shù)最終返回一個對象竭讳,也就是我們所說的 store 對象创葡。主要提供三個方法:getState、dispatch 和 subscribe绢慢。 其中 getState() 獲得 state 對象樹灿渴。dispatch(actionCreator) 用以執(zhí)行 actionCreators,建起從 action 到 store 的橋梁胰舆。

僅僅完成狀態(tài)的變更可不算完骚露,我們還得讓視圖層跟上 store 的變化,于是 Redux 還為 store 設計了 subscribe 方法缚窿。顧名思義棘幸,當 store 更新時,store.subscribe() 的回調(diào)函數(shù)會更新視圖層倦零,以達到 “訂閱” 的效果误续。

在 React 中,有 react-redux 這樣的橋接庫為 Redux 的融入鋪平道路扫茅。所以蹋嵌,我們只需為頂層容器組件外包一層 Provider 組件、再配合 connect 函數(shù)處理從 store 變更到 view 渲染的相關(guān)過程葫隙。

import store from './store';
import {connect, Provider} from 'react-redux';
import React from 'react';
import ReactDOM from 'react-dom';
import Page from '../components/page'; //業(yè)務組件
// 把 state 映射到 Container 組件的 props 上的函數(shù)
const mapStateToProps = state => { 
    return {
        ...state
    }
}
const Container = connect(mapStateToProps)(Page); //頂層容器組件
ReactDOM.render(
    <Provider store={store}>
        <Container />
    </Provider>,
    document.getElementById("root")
);

而頂層容器組件往下的子組件只需憑借 props 就能一層層地拿到 store 數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)了栽烂。就像這樣:

store props

至此,我們走了一遍完整的數(shù)據(jù)流恋脚。然而腺办,在實際項目中,我們面臨的需求更為復雜糟描,與此同時菇晃,redux 和 react 又是具有強大擴展性的庫,接下來我們將結(jié)合以上的主體思路蚓挤,談談我們在實際開發(fā)中會遇到的一些細節(jié)問題磺送。

2. 細節(jié)

應用目錄

清晰的思路須輔以分工明確的文件模塊驻子,才能讓我們的應用達到更佳的實踐效果,同時估灿,統(tǒng)一的結(jié)構(gòu)也便于腳手架生成模板崇呵,提高開發(fā)效率。

以下的目錄結(jié)構(gòu)為團隊伙伴多次探討和改進而來(限于篇幅馅袁,這里只關(guān)注 React 應用的目錄域慷。):

appPage
├── components
│   └── wrapper
│       ├── component-a
│       │   ├── images
│       │   ├── index.js
│       │   └── index.scss
│       ├── component-a-a
│       ├── component-a-b
│       ├── component-b
│       └── component-b-a
├── react
│   ├── reducer
│   │   ├── index.js
│   │   ├── reducerA.js
│   │   └── reducerB.js
│   ├── action.js
│   ├── actionTypes.js
│   ├── bindActions.js
│   ├── container.js
│   ├── model.js
│   ├── param.js
│   └── store.js  
└── app.js

入口文件 app.js 與頂層組件 react/container.js

這塊我們基本上保持和之前思路上的一致,用 react-redux 橋接庫提供的 Provider 與函數(shù) connect 完成 Redux store 到 React state 的轉(zhuǎn)變汗销。

細心的你會在 Provider 的源碼中發(fā)現(xiàn)犹褒,它最終返回的還是子組件(本例中就是頂層容器組件 “Container“ )。星星還是那個星星弛针,Container 還是那個 Container叠骑,只是多了一個 Redux store 對象。

而 Contaier 作為 業(yè)務組件 Wrapper 的 高階組件 削茁,負責把 Provider 賦予它的 store 通過 store.getState() 獲取數(shù)據(jù)宙枷,轉(zhuǎn)而賦值給 state 芝雪。然后又根據(jù)我們定義的 mapStateToProps 函數(shù)按一定的結(jié)構(gòu)將 state 對接到 props 上屯远。 mapStateToProps 函數(shù)我們稍后詳說。如下所見套鹅,這一步主要是 connect 函數(shù)干的活兒瘾杭。

//入口文件:app.js
import store from './react/store';
import Container from './react/container'; 
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
ReactDOM.render(
    <Provider store={store}>
        <Container />
    </Provider>,
    document.getElementById("root")
);
//頂層容器組件:react/container.js
import mapStateToProps from './param';
import {connect} from 'react-redux';
import Wrapper from '../components/wrapper';
export default connect(mapStateToProps)(Wrapper);

業(yè)務組件 component/Wrapper.js 與 mapStateToProps

這兩個模塊是整個應用很重要的業(yè)務模塊诅病。作為一個復雜應用,將 state 上的數(shù)據(jù)和 actionCreator 合理地分發(fā)到各個業(yè)務組件中粥烁,同時要易于維護贤笆,是開發(fā)的關(guān)鍵。

首先页徐,我們設計 mapStateToProps 函數(shù)。需要謹記一點:拿到的參數(shù)是 connect 函數(shù)交給我們的根 state银萍,返回的對象是最終 this.props 的結(jié)構(gòu)变勇。

和 Redux 官方示例不同的是,我們?yōu)榱丝勺x性贴唇,將分發(fā) action 的函數(shù)也囊括進這個結(jié)構(gòu)中搀绣。這也是得益于 bindActions 模塊,稍后我們會講到戳气。

//mapStateToProps:react/param.js
import bindActions from './bindActions';
const mapStateToProps = state => {
    let {demoAPP} = state; // demoAPP 也是 reducer 中的同名函數(shù)
    // 分發(fā) action 的函數(shù)
    let {initDemoAPP, setDemoAPP} = bindActions;
    // 分發(fā) state 上的數(shù)據(jù)
    let {isLoading, dataForA, dataForB} = demoAPP;
    let {dataForAA1, dataForAA2, dataForAB} = dataForA; 
    // 返回的對象即為 Wrapper 組件的 this.props
    return {
        initDemoAPP, // Wrapper 組件需要發(fā)送一個 action 初始化頁面數(shù)據(jù)
        isLoading, //  Wrapper 組件需要 isLoading 用于展示
        paramsComponentA: {
            dataForA, // 組件 A 需要 dataForA 用于展示
            paramsComponentAA: {
                setDemoAPP, // 組件 AA 需要發(fā)送一個 action 修改數(shù)據(jù)
                dataForAA1,
                dataForAA2
            },
            paramsComponentAB: {
                dataForAB
            }
        },
        paramsComponentB: {
            dataForB,
            paramsComponentBA: {}
        }
    }
}
export default mapStateToProps;

這樣链患,我們這個函數(shù)就準備好履行它分發(fā)數(shù)據(jù)和組件行為的職責了。那么瓶您,它又該如何 “服役” 呢麻捻?

敏銳的你一定察覺到剛才我們設計的結(jié)構(gòu)中纲仍,以 “ params ” 開頭的屬性既沒起到給組件展示數(shù)據(jù)的作用,又沒有為組件發(fā)送 action 的功能贸毕。它們便是我們分發(fā)以上兩種功能屬性的關(guān)鍵郑叠。

我們先來看看業(yè)務組件 Wrapper :

//業(yè)務組件組件:components/wrapper.js
import React, { Component } from 'react';
import ComponentA from '../component-a';
import ComponentB from '../component-b';
export default class Example extends Component {
    constructor(props) {
        super(props);
    }
    componentDidMount() {
        this.props.initDemoAPP(); //拉取業(yè)務數(shù)據(jù)
    }
    render() {
        let {paramsComponentA, paramsComponentB, isLoading} = this.props;

        if (isLoading) {
            return (<span>App is loading ...</span>);
        }
        return (
            <div>
                {/* 為組件分發(fā)參數(shù) */}
                <ComponentA {...paramsComponentA}/>
                <ComponentB {...paramsComponentB}/>
            </div>
        );
    }
}

現(xiàn)在,param 屬性們?yōu)槲覀冋故玖怂缪莸慕巧涸诮M件中實際分發(fā)數(shù)據(jù)和方法的快遞小哥明棍。這樣乡革,即使項目越變越大,組件嵌套越來越多摊腋,我們也能在 param.js 模塊中沸版,清晰地看到我們的組件結(jié)構(gòu)。需求更改的時候兴蒸,我們也能快速地定位和修改视粮,而不用對著堆積如山的組件模塊梳理父子關(guān)系。

相信你應該能猜到剩下的子組件們怎么取到數(shù)據(jù)了类咧,這里限于篇幅就不貼出它們的代碼了馒铃。

Action 模塊: react/action.js、react/actionType.js 和 react/bindActions.js

在前面的介紹中痕惋,我們提到:一個 ActionCreator 長這樣:

const addTodo = text => ({
    type: 'ADD_TODO',
    text
});

而在 Redux 中区宇,真正讓其分發(fā)一個 action ,并讓 store 響應該 action值戳,依靠的是 dispatch 方法议谷,即:

store.dispatch(addTodo('new todo item'));

交互動作一多,就會變成:

store.dispatch(addTodo('new todo item1'));
store.dispatch(deleteTodo(0));
store.dispatch(compeleteTodo(1));
store.dispatch(clearTodos());
//...

而容易想到:抽象出一個公用函數(shù)來分發(fā) action (這里粗略寫一下我的思路堕虹,簡化方式并不唯一)

const {dispatch} = store;
const dispatcher = (actionCreators, dispatch) => {
    // ...校驗參數(shù)
    let bounds = {};
    let keys = Object.keys(actionCreators);
    for (let key of keys) {
        bounds[key] = (...rest) => {
            dispatch(actionCreators[key].apply(null, rest));
        }
    }
    return bounds;
}
//簡化后的使用方式
const disp = dispatcher({
    addTodo,
    deleteTodo,
    compeleteTodo
    //...
}, dispatch);
disp.addTodo('new todo item1');
disp.deleteTodo(0);
//...

而細心的 Redux 已經(jīng)為我們提供了這個方法 —— bindActionCreator

所以卧晓,我們的 bindActions.js 模塊就借用了 bindActionCreator 來簡化 action 的分發(fā):

// react/bindActions.js
import store from './store.js';
import {bindActionCreators} from 'redux';
import * as actionCreators from './action';
let {dispatch} = store;
export default bindActionCreators({ ...actionCreators}, dispatch);

不難想象,action 模塊里就是一個個 actionCreator :

// react/action.js
import * as types from '/actionType.js';
export const setDemoAPP = payload => ({
    type: types.SET_DEMO_APP,
    payload
});
// 其他 actionCreators ...

為了更好地合作赴捞,我們單獨為 action 的 type 劃分了一個模塊 —— actionTypes.js 里面看起來會比較無聊:

// react/actionTypes.js
export const SET_DEMO_APP = "SET_DEMO_APP";
// 其他 types ...

react/reducers/ 和 react/store.js

前面我們說到逼裆,reducer 的作用就是區(qū)別 action type 然后更新 state ,這里不再贅述赦政∈び睿可上手實際項目的時候,你會發(fā)現(xiàn) action 類型和對應處理方式多起來會讓單個 reducer 迅速龐大恢着。

為此桐愉,我們就得想方設法將其按業(yè)務邏輯拆分,以免難以維護掰派。但是如何把拆分后的 Reducer 組合起來呢 Redux 再次為我們提供便捷 —— combineReducers 从诲。

只有單一 Reducer 時,想必代碼結(jié)構(gòu)你也了然:

import * as actionTypes from '../actionTypes';
let initState = {
    isLoading: true
};
// 對應 state.demoAPP
const demoAPP = (state = initState, action) => {
    switch (action.type) {
        case actionTypes.SET_DEMO_APP:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
export default demoAPP; // 把它轉(zhuǎn)交給 createStore 函數(shù)

我們最終得到的 state 結(jié)構(gòu)是:

  • state
    • demoAPP

當有多個 reducer 時:

import * as actionTypes from '../actionTypes';
import { combineReducers } from 'redux';
let initState = {
    isLoading: true
};
// 對應 state.demoAPP
const demoAPP = (state = initState, action) => {
    switch (action.type) {
        case actionTypes.SET_DEMO_APP:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
// 對應 state.reducerB
const reducerB = (state = {}, action) => {
    switch (action.type) {
        case actionTypes.SET_REDUCER_B:
            return {
                isLoading: false,
                ...action.payload
            };
        default:
            return state;
    }
}
const rootReducer = combineReducers({demoAPP,reducerB});
export default rootReducer;

我們最終得到的 state 結(jié)構(gòu)是:

  • state
    • demoAPP
    • reducerB

想必你已經(jīng)想到更進一步靡羡,把這些 Reducer 拆分到相應的文件模塊下:

// react/reducers/index.js 
import demoAPP from './demoAPP.js';
import reducerB from './reducerB.js';
const rootReducer = combineReducers({demoAPP,reducerB});
export default rootReducer;

接著系洛,我們來看 store 模塊:

// react/store.js
import rootReducer from './reducers';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
const initialState = {};
const finalCreateStore = compose(
    applyMiddleware(thunk)
)(createStore);
export default finalCreateStore(rootReducer, initialState);

怎么和想象的不一樣俊性?不應該是這樣嗎:

// react/store.js
import rootReducer from './reducers';
import { createStore } from 'redux';
export default createStore(rootReducer);

這里引入 redux 中間件的概念,你只需知道 redux 中間件的作用就是 在 action 發(fā)出以后碎罚,給我們一個再加工 action 的機會 就可以了磅废。

為什么要引入 redux-thunk 這個中間件呢?

要知道荆烈,我們此前所討論的都是同步過程拯勉。實際項目中,只要遇到請求接口的場景(當然不只有這種場景)就要去處理異步過程憔购。

前面我們知道宫峦,dispatch 一個 ActionCreator 會立即返回一個 action 對象,用以更新數(shù)據(jù)玫鸟,而中間件賦予我們再處理 action 的機會导绷。

試想一下,如果我們在這個過程中屎飘,發(fā)現(xiàn) ActionCreator 返回的并不是一個 action 對象妥曲,而是一個函數(shù),然后通過這個函數(shù)請求接口钦购,響應就緒后檐盟,我們再 dispatch 一個 ActionCreator ,這次我們真的返回一個 action 押桃,然后攜帶接口返回的數(shù)據(jù)去更新 state 葵萎。 這樣一來不就解決了我們的問題嗎?

當然唱凯,這只是基本思路羡忘,關(guān)于 redux 的中間件設計,又是一個有趣的話題磕昼,有興趣我們可以再開一篇專門討論卷雕,這里點到為止。

回到我們的話題票从,經(jīng)過

const finalCreateStore = compose(
    applyMiddleware(thunk)
)(createStore);
export default finalCreateStore(rootReducer, initialState);

這樣包裝一遍 store 后漫雕,我們就可以愉快地使用異步 action 了:

// react/action.js
import * as types from './actionType.js';
import * as model from './model.js';
// 同步 actionCreator
export const setDemoAPP = payload => ({
    type: types.SET_DEMO_APP,
    payload
});
// 異步 actionCreator
export const initDemoAPP = () => dispatch => {
    model.getBaseData().then(response => {
        let {status, data} = response;
        if (status === 0) {
            //請求成功且返回數(shù)據(jù)正常
            dispatch(setDemoAPP(data));
        }
    }, error => {
        // 處理請求異常的情況
    });
}

這里我們用 promise 方式來處理請求,model.js 模塊如你所想是一些接口請求 promise纫骑,就像這樣:

export const getBaseData () => {
    return $.getJSON('/someAPI');
}

你也可以參閱我們往期介紹的其他方式蝎亚。

最后九孩,我們再來完善一下之前的流程:

redux flow
redux flow

3.結(jié)語

Redux 的 API 一只手都能數(shù)得完先馆,源碼更是精煉,加起來不超過500行躺彬。但它給我們帶來的煤墙,不啻是一套復雜應用解決方案梅惯,更是 Flux 思想的精簡表達。此外仿野,你還可以從中體會到函數(shù)式編程的樂趣铣减。

一千個觀眾心中有一千個哈姆萊特,你腦海里的又是哪一個呢脚作?

參考

《Redux 官方文檔》
《深入 React 技術(shù)椇》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市球涛,隨后出現(xiàn)的幾起案子劣针,更是在濱河造成了極大的恐慌,老刑警劉巖亿扁,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捺典,死亡現(xiàn)場離奇詭異,居然都是意外死亡从祝,警方通過查閱死者的電腦和手機襟己,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牍陌,“玉大人擎浴,你說我怎么就攤上這事∧派模” “怎么了退客?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長链嘀。 經(jīng)常有香客問我萌狂,道長,這世上最難降的妖魔是什么怀泊? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任茫藏,我火速辦了婚禮,結(jié)果婚禮上霹琼,老公的妹妹穿的比我還像新娘务傲。我一直安慰自己,他們只是感情好枣申,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布售葡。 她就那樣靜靜地躺著,像睡著了一般忠藤。 火紅的嫁衣襯著肌膚如雪挟伙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天模孩,我揣著相機與錄音尖阔,去河邊找鬼贮缅。 笑死,一個胖子當著我的面吹牛介却,可吹牛的內(nèi)容都是我干的谴供。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼齿坷,長吁一口氣:“原來是場噩夢啊……” “哼桂肌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起永淌,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤轴或,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仰禀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體照雁,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年答恶,在試婚紗的時候發(fā)現(xiàn)自己被綠了饺蚊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悬嗓,死狀恐怖污呼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情包竹,我是刑警寧澤燕酷,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站周瞎,受9級特大地震影響苗缩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜声诸,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一酱讶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彼乌,春花似錦泻肯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毒租,卻和暖如春稚铣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工榛泛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人噩斟。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓曹锨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剃允。 傳聞我的和親對象是個殘疾皇子沛简,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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