React-Redux分析

React-Redux分析

Redux弱贼,作為大型React應(yīng)用狀態(tài)管理最常用的工具,其概念理論和實(shí)踐都是很值得我們學(xué)習(xí)呕诉,分析然后在實(shí)踐中深入了解的猖毫,對前端開發(fā)者能力成長很有幫助。本篇計(jì)劃結(jié)合Redux容器組件和展示型組件的區(qū)別對比以及Redux與React應(yīng)用最常見的連接庫镐依,react-redux源碼分析匹涮,以期達(dá)到對Redux和React應(yīng)用的更深層次理解。

前言

react-redux庫提供Provider組件通過context方式向應(yīng)用注入store槐壳,然后可以使用connect高階方法然低,獲取并監(jiān)聽store,然后根據(jù)store state和組件自身props計(jì)算得到新props,注入該組件雳攘,并且可以通過監(jiān)聽store带兜,比較計(jì)算出的新props判斷是否需要更新組件。

react與redux應(yīng)用結(jié)構(gòu)

Provider

首先吨灭,react-redux庫提供Provider組件將store注入整個(gè)React應(yīng)用的某個(gè)入口組件刚照,通常是應(yīng)用的頂層組件。Provider組件使用context向下傳遞store:

// 內(nèi)部組件獲取redux store的鍵
const storeKey = 'store'
// 內(nèi)部組件
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
  // 聲明context喧兄,注入store和可選的發(fā)布訂閱對象
  getChildContext() {
    return { [storeKey]: this[storeKey], [subscriptionKey]: null }
  }

  constructor(props, context) {
    super(props, context)
    // 緩存store
    this[storeKey] = props.store;
  }

  render() {
    // 渲染輸出內(nèi)容
    return Children.only(this.props.children)
  }
}

Example

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './components/App'
import reducers from './reducers'

// 創(chuàng)建store
const store = createStore(todoApp, reducers)

// 傳遞store作為props給Provider組件无畔;
// Provider將使用context方式向下傳遞store
// App組件是我們的應(yīng)用頂層組件
render(
  <Provider store={store}>
    <App/>
  </Provider>, document.getElementById('app-node')
)

connect方法

在前面我們使用Provider組件將redux store注入應(yīng)用,接下來需要做的是連接組件和store吠冤。而且我們知道Redux不提供直接操作store state的方式浑彰,我們只能通過其getState訪問數(shù)據(jù),或通過dispatch一個(gè)action來改變store state拯辙。

這也正是react-redux提供的connect高階方法所提供的能力郭变。

Example

container/TodoList.js

首先我們創(chuàng)建一個(gè)列表容器組件,在組件內(nèi)負(fù)責(zé)獲取todo列表薄风,然后將todos傳遞給TodoList展示型組件饵较,同時(shí)傳遞事件回調(diào)函數(shù),展示型組件觸發(fā)諸如點(diǎn)擊等事件時(shí)遭赂,調(diào)用對應(yīng)回調(diào)循诉,這些回調(diào)函數(shù)內(nèi)通過dispatch actions來更新redux store state,而最終將store和展示型組件連接起來使用的是react-redux的connect方法撇他,該方法接收

import {connect} from 'react-redux'
import TodoList from 'components/TodoList.jsx'

class TodoListContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {todos: null, filter: null}
  }
  handleUpdateClick (todo) {
    this.props.update(todo);  
  }
  componentDidMount() {
    const { todos, filter, actions } = this.props
    if (todos.length === 0) {
      this.props.fetchTodoList(filter);
    }
  render () {
    const { todos, filter } = this.props

    return (
      <TodoList 
        todos={todos}
        filter={filter}
        handleUpdateClick={this.handleUpdateClick}
        /* others */
      />
    )
  }
}

const mapStateToProps = state => {
  return {
    todos : state.todos,
    filter: state.filter
  }
}

const mapDispatchToProps = dispatch => {
  return {
    update : (todo) => dispatch({
      type : 'UPDATE_TODO',
      payload: todo
    }),
    fetchTodoList: (filters) => dispatch({
      type : 'FETCH_TODOS',
      payload: filters
    })
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoListContainer)

components/TodoList.js

import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, handleUpdateClick }) => (
  <ul>
    {todos.map(todo => (
      <Todo key={todo.id} {...todo} handleUpdateClick={handleUpdateClick} />
    ))}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.array.isRequired
  ).isRequired,
  handleUpdateClick: PropTypes.func.isRequired
}

export default TodoList

components/Todo.js

import React from 'react'
import PropTypes from 'prop-types'

class Todo extends React.Component { 
  constructor(...args) {
    super(..args);
    this.state = {
      editable: false,
      todo: this.props.todo
    }
  }
  handleClick (e) {
    this.setState({
      editable: !this.state.editable
    })
  }
  update () {
    this.props.handleUpdateClick({
      ...this.state.todo
      text: this.refs.content.innerText
    })
  }
  render () {
    return (
      <li
        onClick={this.handleClick}
        style={{
          contentEditable: editable ? 'true' : 'false'
        }}
      >
        <p ref="content">{text}</p>
        <button onClick={this.update}>Save</button>
      </li>
    )
  }

Todo.propTypes = {
  handleUpdateClick: PropTypes.func.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo

容器組件與展示型組件

在使用Redux作為React應(yīng)用的狀態(tài)管理容器時(shí)茄猫,通常貫徹將組件劃分為容器組件(Container Components)和展示型組件(Presentational Components)的做法,

Presentational Components Container Components
目標(biāo) UI展示 (HTML結(jié)構(gòu)和樣式) 業(yè)務(wù)邏輯(獲取數(shù)據(jù)困肩,更新狀態(tài))
感知Redux
數(shù)據(jù)來源 props 訂閱Redux store
變更數(shù)據(jù) 調(diào)用props傳遞的回調(diào)函數(shù) Dispatch Redux actions
可重用 獨(dú)立性強(qiáng) 業(yè)務(wù)耦合度高

應(yīng)用中大部分代碼是在編寫展示型組件划纽,然后使用一些容器組件將這些展示型組件和Redux store連接起來。

connect()源碼分析

react-redux源碼邏輯
connectHOC = connectAdvanced;
mergePropsFactories = defaultMergePropsFactories锌畸;
selectorFactory = defaultSelectorFactory;
function connect (
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  {
  pure = true,
  areStatesEqual = strictEqual, // 嚴(yán)格比較是否相等
  areOwnPropsEqual = shallowEqual, // 淺比較
  areStatePropsEqual = shallowEqual,
  areMergedPropsEqual = shallowEqual,
  renderCountProp, // 傳遞給內(nèi)部組件的props鍵勇劣,表示render方法調(diào)用次數(shù)
  // props/context 獲取store的鍵
  storeKey = 'store',
  ...extraOptions
  } = {}
) {
  const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
  const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
  const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
  
  // 調(diào)用connectHOC方法
  connectHOC(selectorFactory, {
    // 如果mapStateToProps為false,則不監(jiān)聽store state
    shouldHandleStateChanges: Boolean(mapStateToProps),
    // 傳遞給selectorFactory
    initMapStateToProps,
    initMapDispatchToProps,
    initMergeProps,
    pure,
    areStatesEqual,
    areOwnPropsEqual,
    areStatePropsEqual,
    areMergedPropsEqual,
    renderCountProp, // 傳遞給內(nèi)部組件的props鍵潭枣,表示render方法調(diào)用次數(shù)
    // props/context 獲取store的鍵
    storeKey = 'store',
    ...extraOptions // 其他配置項(xiàng)
  });
}

strictEquall

function strictEqual(a, b) { return a === b }

shallowEquall

源碼

const hasOwn = Object.prototype.hasOwnProperty

function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) ||
        !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}
shallowEqual({x:{}},{x:{}}) // false
shallowEqual({x:1},{x:1}) // true

connectAdvanced高階函數(shù)

源碼

function connectAdvanced (
  selectorFactory,
  {
    renderCountProp = undefined, // 傳遞給內(nèi)部組件的props鍵比默,表示render方法調(diào)用次數(shù)
    // props/context 獲取store的鍵
    storeKey = 'store',
    ...connectOptions
  } = {}
) {
  // 獲取發(fā)布訂閱器的鍵
  const subscriptionKey = storeKey + 'Subscription';
  const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  };
  const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
  };
  
  return function wrapWithConnect (WrappedComponent) {
    const selectorFactoryOptions = {
      // 如果mapStateToProps為false,則不監(jiān)聽store state
      shouldHandleStateChanges: Boolean(mapStateToProps),
      // 傳遞給selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      ...connectOptions,
      ...others
      renderCountProp, // render調(diào)用次數(shù)
      shouldHandleStateChanges, // 是否監(jiān)聽store state變更
      storeKey,
      WrappedComponent
    }
    
    // 返回拓展過props屬性的Connect組件
    return hoistStatics(Connect, WrappedComponent)
  }
}

selectorFactory

selectorFactory函數(shù)返回一個(gè)selector函數(shù)盆犁,根據(jù)store state, 展示型組件props,和dispatch計(jì)算得到新props命咐,最后注入容器組件,selectorFactory函數(shù)結(jié)構(gòu)形如:

(dispatch, options) => (state, props) => ({
  thing: state.things[props.thingId],
  saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
})

注:redux中的state通常指redux store的state而不是組件的state谐岁,另此處的props為傳入組件wrapperComponent的props醋奠。

源碼

function defaultSelectorFactory (dispatch, {
  initMapStateToProps,
  initMapDispatchToProps,
  initMergeProps,
  ...options
}) {
  const mapStateToProps = initMapStateToProps(dispatch, options)
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
  const mergeProps = initMergeProps(dispatch, options)
  
  // pure為true表示selectorFactory返回的selector將緩存結(jié)果榛臼;
  // 否則其總是返回一個(gè)新對象
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  // 最終執(zhí)行selector工廠函數(shù)返回一個(gè)selector
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  );
}

pureFinalPropsSelectorFactory

function pureFinalPropsSelectorFactory (
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps
  
  // 返回合并后的props或state
  // handleSubsequentCalls變更后合并;handleFirstCall初次調(diào)用
  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
    : handleFirstCall(nextState, nextOwnProps)
  }  
}

handleFirstCall

function handleFirstCall(firstState, firstOwnProps) {
  state = firstState
  ownProps = firstOwnProps
  stateProps = mapStateToProps(state, ownProps) // store state映射到組件的props
  dispatchProps = mapDispatchToProps(dispatch, ownProps)
  mergedProps = mergeProps(stateProps, dispatchProps, ownProps) // 合并后的props
  hasRunAtLeastOnce = true
  return mergedProps
}

defaultMergeProps

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  // 默認(rèn)合并props函數(shù)
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

handleSubsequentCalls

function handleSubsequentCalls(nextState, nextOwnProps) {
  // shallowEqual淺比較
  const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
  // 深比較
  const stateChanged = !areStatesEqual(nextState, state)
  state = nextState
  ownProps = nextOwnProps

  // 處理props或state變更后的合并
  // store state及組件props變更
  if (propsChanged && stateChanged) return handleNewPropsAndNewState()
  if (propsChanged) return handleNewProps()
  if (stateChanged) return handleNewState()
  
  return mergedProps
}

計(jì)算返回新props

只要展示型組件自身props發(fā)生變更窜司,則需要重新返回新合并props沛善,然后更新容器組件,無論store state是否變更:

// 只有展示型組件props變更
function handleNewProps() {
  // mapStateToProps計(jì)算是否依賴于展示型組件props
  if (mapStateToProps.dependsOnOwnProps)
    stateProps = mapStateToProps(state, ownProps)
  // mapDispatchToProps計(jì)算是否依賴于展示型組件props
  if (mapDispatchToProps.dependsOnOwnProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)
  
  mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
  
  return mergedProps
}
// 展示型組件props和store state均變更
function handleNewPropsAndNewState() {
  stateProps = mapStateToProps(state, ownProps)
  // mapDispatchToProps計(jì)算是否依賴于展示型組件props
  if (mapDispatchToProps.dependsOnOwnProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)
  
  mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
  
  return mergedProps
}

計(jì)算返回stateProps

通常容器組件props變更由store state變更推動(dòng)例证,所以只有store state變更的情況較多路呜,而且此處也正是使用Immutable時(shí)需要注意的地方:不要在mapStateToProps方法內(nèi)使用toJS()方法迷捧。

當(dāng)mapStateToProps兩次返回的props對象未有變更時(shí)织咧,不需要重新計(jì)算,直接返回之前合并得到的props對象即可漠秋,之后在selector追蹤對象中比較兩次selector函數(shù)返回值是否有變更時(shí)笙蒙,將返回false,容器組件不會(huì)觸發(fā)變更庆锦。

因?yàn)閷Ρ榷啻蝝apStateToProps返回的結(jié)果時(shí)是使用淺比較捅位,所以不推薦使用Immutable.toJS()方法,其每次均返回一個(gè)新對象搂抒,對比將返回false艇搀,而如果使用Immutable且其內(nèi)容未變更,則會(huì)返回true求晶,可以減少不必要的重新渲染焰雕。

// 只有store state變更
function handleNewState() {
  const nextStateProps = mapStateToProps(state, ownProps)
  // 淺比較
  const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
  stateProps = nextStateProps

  // 計(jì)算得到的新props變更了,才需要重新計(jì)算返回新的合并props
  if (statePropsChanged) {
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
  }

  // 若新stateProps未發(fā)生變更芳杏,則直接返回上一次計(jì)算得出的合并props矩屁;
  // 之后selector追蹤對象比較兩次返回值是否有變更時(shí)將返回false;
  // 否則返回使用mergeProps()方法新合并得到的props對象爵赵,變更比較將返回true
  return mergedProps
}

hoist-non-react-statics

類似Object.assign吝秕,將子組件的非React的靜態(tài)屬性或方法復(fù)制到父組件,React相關(guān)屬性或方法不會(huì)被覆蓋而是合并空幻。

hoistStatics(Connect, WrappedComponent)

Connect Component

真正的Connect高階組件烁峭,連接redux store state和傳入組件,即將store state映射到組件props秕铛,react-redux使用Provider組件通過context方式注入store约郁,然后Connect組件通過context接收store,并添加對store的訂閱:

class Connect extends Component {
  constructor(props, context) {
    super(props, context)

    this.state = {}
    this.renderCount = 0 // render調(diào)用次數(shù)初始為0
    // 獲取store如捅,props或context方式
    this.store = props[storeKey] || context[storeKey]
    // 是否使用props方式傳遞store
    this.propsMode = Boolean(props[storeKey])

    // 初始化selector
    this.initSelector()
    // 初始化store訂閱
    this.initSubscription()
  }
  
  componentDidMount() {
    // 不需要監(jiān)聽state變更
    if (!shouldHandleStateChanges) return
    // 發(fā)布訂閱器執(zhí)行訂閱
    this.subscription.trySubscribe()
    // 執(zhí)行selector
    this.selector.run(this.props)
    // 若還需要更新棍现,則強(qiáng)制更新
    if (this.selector.shouldComponentUpdate) this.forceUpdate()
  }
  
  // 渲染組件元素
  render() {
    const selector = this.selector
    selector.shouldComponentUpdate = false; // 重置是否需要更新為默認(rèn)的false

    // 將redux store state轉(zhuǎn)化映射得到的props合并入傳入的組件
    return createElement(WrappedComponent, this.addExtraProps(selector.props))
  }
}

addExtraProps()

給props添加額外的props屬性:

// 添加額外的props
addExtraProps(props) {
  const withExtras = { ...props }
  if (renderCountProp) withExtras[renderCountProp] = this.renderCount++;// render 調(diào)用次數(shù)
  if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription

  return withExtras
}

初始化selector追蹤對象initSelector

Selector,選擇器镜遣,根據(jù)redux store state和組件的自身props己肮,計(jì)算出將注入該組件的新props士袄,并緩存新props,之后再次執(zhí)行選擇器時(shí)通過對比得出的props谎僻,決定是否需要更新組件娄柳,若props變更則更新組件,否則不更新艘绍。

使用initSelector方法初始化selector追蹤對象及相關(guān)狀態(tài)和數(shù)據(jù):

// 初始化selector
initSelector() {
  // 使用selector工廠函數(shù)創(chuàng)建一個(gè)selector
  const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
  // 連接組件的selector和redux store state
  this.selector = makeSelectorStateful(sourceSelector, this.store)
  // 執(zhí)行組件的selector函數(shù)
  this.selector.run(this.props)
}

makeSelectorStateful()

創(chuàng)建selector追蹤對象以追蹤(tracking)selector函數(shù)返回結(jié)果:

function makeSelectorStateful(sourceSelector, store) {
  // 返回selector追蹤對象赤拒,追蹤傳入的selector(sourceSelector)返回的結(jié)果
  const selector = {
    // 執(zhí)行組件的selector函數(shù)
    run: function runComponentSelector(props) {
      // 根據(jù)store state和組件props執(zhí)行傳入的selector函數(shù),計(jì)算得到nextProps
      const nextProps = sourceSelector(store.getState(), props)
      // 比較nextProps和緩存的props;
      // false诱鞠,則更新所緩存的props并標(biāo)記selector需要更新
      if (nextProps !== selector.props || selector.error) {
        selector.shouldComponentUpdate = true // 標(biāo)記需要更新
        selector.props = nextProps // 緩存props
        selector.error = null
      }  
    }
  }

  // 返回selector追蹤對象
  return selector
}

初始化訂閱initSubscription

初始化監(jiān)聽/訂閱redux store state:

// 初始化訂閱
initSubscription() {
  if (!shouldHandleStateChanges) return; // 不需要監(jiān)聽store state

  // 判斷訂閱內(nèi)容傳遞方式:props或context挎挖,兩者不能混雜
  const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
  // 訂閱對象實(shí)例化,并傳入事件回調(diào)函數(shù)
  this.subscription = new Subscription(this.store, 
                                       parentSub,
                                       this.onStateChange.bind(this))
  // 緩存訂閱器發(fā)布方法執(zhí)行的作用域
  this.notifyNestedSubs = this.subscription.notifyNestedSubs
    .bind(this.subscription)
}

訂閱類實(shí)現(xiàn)

組件訂閱store使用的訂閱發(fā)布器實(shí)現(xiàn):

export default class Subscription {
  constructor(store, parentSub, onStateChange) {
    // redux store
    this.store = store
    // 訂閱內(nèi)容
    this.parentSub = parentSub
    // 訂閱內(nèi)容變更后的回調(diào)函數(shù)
    this.onStateChange = onStateChange
    this.unsubscribe = null
    // 訂閱記錄數(shù)組
    this.listeners = nullListeners
  }
  
  // 訂閱
  trySubscribe() {
    if (!this.unsubscribe) {
      // 若傳遞了發(fā)布訂閱器則使用該訂閱器訂閱方法進(jìn)行訂閱
      // 否則使用store的訂閱方法
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.onStateChange)
        : this.store.subscribe(this.onStateChange)
 
      // 創(chuàng)建訂閱集合對象
      // { notify: function, subscribe: function }
      // 內(nèi)部包裝了一個(gè)發(fā)布訂閱器航夺;
      // 分別對應(yīng)發(fā)布(執(zhí)行所有回調(diào))蕉朵,訂閱(在訂閱集合中添加回調(diào))
      this.listeners = createListenerCollection()
    }
  }
  
  // 發(fā)布
  notifyNestedSubs() {
    this.listeners.notify()
  }
}

訂閱回調(diào)函數(shù)

訂閱后執(zhí)行的回調(diào)函數(shù):

onStateChange() {
  // 選擇器執(zhí)行
  this.selector.run(this.props)

  if (!this.selector.shouldComponentUpdate) {
    // 不需要更新則直接發(fā)布
    this.notifyNestedSubs()
  } else {
    // 需要更新則設(shè)置組件componentDidUpdate生命周期方法
    this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
    // 同時(shí)調(diào)用setState觸發(fā)組件更新
    this.setState(dummyState) // dummyState = {}
  }
}

// 在組件componentDidUpdate生命周期方法內(nèi)發(fā)布變更
notifyNestedSubsOnComponentDidUpdate() {
  // 清除組件componentDidUpdate生命周期方法
  this.componentDidUpdate = undefined
  // 發(fā)布
  this.notifyNestedSubs()
}

其他生命周期方法

getChildContext () {
  // 若存在props傳遞了store,則需要對其他從context接收store并訂閱的后代組件隱藏其對于store的訂閱阳掐;
  // 否則將父級的訂閱器映射傳入始衅,給予Connect組件控制發(fā)布變化的順序流
  const subscription = this.propsMode ? null : this.subscription
  return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}
// 接收到新props
componentWillReceiveProps(nextProps) {
  this.selector.run(nextProps)
}

// 是否需要更新組件
shouldComponentUpdate() {
  return this.selector.shouldComponentUpdate
}

componentWillUnmount() {
  // 重置selector
}

參考閱讀

  1. React with redux
  2. Smart and Dumb Components
  3. React Redux Container Pattern
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缭保,隨后出現(xiàn)的幾起案子汛闸,更是在濱河造成了極大的恐慌,老刑警劉巖艺骂,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诸老,死亡現(xiàn)場離奇詭異,居然都是意外死亡彻亲,警方通過查閱死者的電腦和手機(jī)孕锄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苞尝,“玉大人畸肆,你說我怎么就攤上這事≈嬷罚” “怎么了轴脐?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抡砂。 經(jīng)常有香客問我大咱,道長,這世上最難降的妖魔是什么注益? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任碴巾,我火速辦了婚禮,結(jié)果婚禮上丑搔,老公的妹妹穿的比我還像新娘厦瓢。我一直安慰自己提揍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布煮仇。 她就那樣靜靜地躺著劳跃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浙垫。 梳的紋絲不亂的頭發(fā)上刨仑,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機(jī)與錄音夹姥,去河邊找鬼杉武。 笑死,一個(gè)胖子當(dāng)著我的面吹牛佃声,可吹牛的內(nèi)容都是我干的艺智。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼圾亏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了封拧?” 一聲冷哼從身側(cè)響起志鹃,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泽西,沒想到半個(gè)月后曹铃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捧杉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年陕见,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味抖。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡评甜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仔涩,到底是詐尸還是另有隱情忍坷,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布熔脂,位于F島的核電站佩研,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霞揉。R本人自食惡果不足惜旬薯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望适秩。 院中可真熱鬧绊序,春花似錦些侍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淋样,卻和暖如春耗式,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趁猴。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工刊咳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人儡司。 一個(gè)月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓娱挨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捕犬。 傳聞我的和親對象是個(gè)殘疾皇子跷坝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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