React源码02 - 基础知识 React API 一览

1. JSX到JavaScript的转换

<div id="div" key="key">
  <span>1</span>
  <span>2</span>
</div>
React.createElement(
  "div", // 大写开头会当做原生dom标签的字符串,而组件使用大写开头时,这里会成为变量引用
  { id: "div", key: "key" },
  React.createElement("span", null, "1"),
    React.createElement("span", null, "2")
);
createElement(type, config, children) {
  // 从用户传入的 config 中把4种内置的属性 key、ref、self、source 单独挑出来,config 中剩余的参数作为用户的 props。
  // 然后返回使用 ReactElement 创建的 element 对象。
return ReactElement( type,  key,  ref,  self,  source,  ReactCurrentOwner.current,  props, );
}

2. ReactElement

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type, // 可能是原生dom标签字符串如'span',也可能是一个class类(class组件),还可能是一个function(函数式组件),也可能是 forwardRef 返回的对象,或者 symbol 标记等 
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    // 略
  }

  return element;
};
React.createElement(
  "div",
  { id: "div", key: "key" },
  React.createElement("span", null, "1"),
    React.createElement("span", null, "2")
);

// 于是最初的 jsx 经过 React.createElement() 后成为了下面的对象树,
// 也是函数式组件返回的东西,也是 class 组件组件 render() 方法返回的东西
element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: {
        userProps1: userProps1,
        // ... 用户其他的自定义props
        children: [
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: type,
              key: key,
              ref: ref,
              props: props,
              _owner: owner,
            }, 
            {
              $$typeof: REACT_ELEMENT_TYPE,
              type: type,
              key: key,
              ref: ref,
              props: props,
              _owner: owner,
            }
        ]
    },
    _owner: owner,
};

3. 基类 React.Component

Component 类可能不是想象中那样用于渲染子组件什么的,只是做一些绑定工作:

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

// PureComponent 继承了 Component
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
// pureComponentPrototype.isPureReactComponent = true;

// 常规的 setState,这里只是入队列
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

// 可主动强制更新,这里也只是入队列
// enqueueForceUpdate 内部实现了 ”重载”
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

4. createRef & ref

ref 用于获取 dom 节点或者 class component 的实例。
有三种用法,第一种 string 方法不推荐使用,后面在 React 17 中应该会被废弃:

import React from 'react'

export default class RefDemo extends React.Component {
  constructor() {
    super()
    this.objRef = React.createRef()

    // { current: null }
  }

  componentDidMount() {
    setTimeout(() => {
      this.refs.stringRef.textContent = 'string ref got'
      this.methodRef.textContent = 'method ref got'
      this.objRef.current.textContent = 'obj ref got'
    }, 1000)
  }

  render() {

    return (
      <>
        <p ref="stringRef">span1</p>
        <p ref={ele => (this.methodRef = ele)}>span3</p>
        <p ref={this.objRef}>span3</p>
      </>
    )
  }
}

// export default () => {
//   return <div>Ref</div>
// }

Object.seal() 密封,阻止目标对象上增删属性,并且关闭所有属性的 configurable,但仍可以修改现有的、是 writable 的属性。
Object.freeze() 冻结,比密封更严格,会将 writable 也关闭,意味着现有属性“不可以”修改。但如果属性本身是个引用类型,比如 const object = {a: 1, b: []},那么即使冻结 object 后,object.b.push(666) 也是可以的。
另外,冻结也会冻结目标对象上的 prototype 原型对象。

createRef 源码:

import type {RefObject} from 'shared/ReactTypes';

// an immutable object with a single mutable value
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}

5. forwardRef

字面意思,转发 ref ?为什么 ref 需要转发,不能像上面那样直接使用?

dom 节点或者 class component,是可以由 react 在组件渲染之后把实例绑定(或提供)至我们指定的“容器”中,之后我们就可以从“容器”中引用刚才的实例,进行想要的操作。

React.createRef() 返回的就是 { current: null },这个对象就可以理解为一个“容器”,我们以在 jsx 上声明的方式,提供给 react,渲染之后返回我们想要的实例引用。

image

然而 function 函数式组件并没有实例,就是个函数。所以外部用户无差别地尝试为组件提供 ref “容器”希望回头能拿到实例时,如果遇到 function 组件,则会失败、报错。而这种报错可以通过转发 ref 来避免,因为 function 组件没有所谓的实例,但内部至少返回了 dom 或者另外的 class 组件吧,所以把 ref 转发给它们即可。
即外部用户最终拿到的实例引用,其实是函数式组件内层的实例。

自己的组件可能知道报错,不会去直接试图获取 function 组件的 ref,但如果是作为第三方组件库提供给其他 react 用户来调用,则要使用 forwardRef 来转发用户的诉求,并实现他们。

import React from 'react';

class App extends React.Component {
  render() {
    return <div>div</div>;
  }
}

const TargetComponent = React.forwardRef((props, ref) => (
  <input type="text" ref={ref}/>
));

export default class Comp extends React.Component {
  constructor() {
    super();
    this.ref = React.createRef();
    this.ref2 = React.createRef();
  }

  componentDidMount() {
    // 虽然还是声明在 TargetComponent 函数组件上,但最终拿到了有效的实例引用,即内部的 dom 节点
    this.ref.current.value = 'ref get input';
    console.log('this.ref', this.ref);
    console.log('this.ref2', this.ref2);
  }

  render() {
    return <>
      <TargetComponent ref={this.ref}/>
      <App ref={this.ref2} />
    </>;
  }
}

React.forwardRef() 源码:

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  if (__DEV__) {
    // 用户不按规定的使用会抛出异常信息
  }
  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}

可以看到向 forwardRef 传入的 render 函数至少没在这里被调用,只是用对象包了一层,并增加了一个 $$typeof 属性,值是个 symbol。所以上面例子中声明实际等价于:

const TargetComponent = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render: (props, ref) => (
    <input type="text" ref={ref}/>
    )
};

// 经过 React.createElement() 创建出的 element:
const element = {
  // This tag allows us to uniquely identify this as a React Element
  $$typeof: REACT_ELEMENT_TYPE,
  type: TargetComponent,
  key: null,
  ref: React.createRef(),
  props: {},
  // Record the component responsible for creating this element.
  _owner: owner,
};

6. Context

使用 context 也有两种方式,childContextType 和 createContext。
childContextType 是老的方式,在将来的 React 17 中应该会被废弃,所以优先使用 createContext。

import React from 'react'

const MyContext = React.createContext('default');
const { Provider, Consumer } = MyContext;

class Parent extends React.Component {
  state = {
    newContext: '123',
  };

  render() {
    return (
      <>
        <div>
          <label>newContext:</label>
          <input
            type="text"
            value={this.state.newContext}
            onChange={e => this.setState({ newContext: e.target.value })}
          />
        </div>
        <Provider value={this.state.newContext}>{this.props.children}</Provider>
      </>
    )
  }
}

class Parent2 extends React.Component {
  render() {
    return this.props.children
  }
}

function Child1(props, context) {
  console.log(MyContext);
  console.log(context);
  return <Consumer>{value => <p>newContext: {value}</p>}</Consumer>
}

export default () => (
  <Parent>
    <Parent2>
      <Child1 />
    </Parent2>
  </Parent>
);

React.createContext() 源码:
返回的 context 对象中有 $$typeof: REACT_CONTEXT_TYPE ,且有 Provider 和 Consumer,Provider 只是用对象包了一下原 context,添加了 $$typeof: REACT_PROVIDER_TYPE 属性;而 Consumer 压根就是引用原 context。有点俄罗斯套娃的感觉,能想到的就是 $$typeof 会作为 react 更新时判断不同类型的依据。而套娃的操作,可能就是为了方便操作 context,引来引去总能找到那个 context。


export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    if (__DEV__) {
       // 略
      );
    }
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    // As a workaround to support multiple concurrent renderers, we categorize
    // some renderers as primary and others as secondary. We only expect
    // there to be two concurrent renderers at most: React Native (primary) and
    // Fabric (secondary); React DOM (primary) and React ART (secondary).
    // Secondary renderers store their context values on separate fields.
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    // These are circular
    Provider: (null: any),
    Consumer: (null: any),
  };

  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  if (__DEV__) {
        // 异常处理
  } else {
    context.Consumer = context;
  }

  return context;
}

打印 React.createContext() 返回的 context 对象,验证套娃操作:


image

7. ConcurrentMode

在 React 16.6 提出,让 react 整体渲染过程可以根据优先级排列,可以任务调度、可以中断渲染过程等。来提高渲染性能,减少页面卡顿。

flushSync 使用优先级最高的方式进行更新,用来提高 this.setState 优先级。

使用 ConcurrentMode 包裹的组件都是低优先级的,所以为了演示高低优先级带来的区别感受,对比使用 flushSync。

例子中使用 flushSync 时,setState 优先级最高,基本是立即更新,这也导致动画卡顿明显,因为 200ms setState 间隔太快了,可能还没来得及渲染完,又要更新。因此使用 ConcurrentMode 把更新的优先级降低,从而不会频繁更新动画,显得流畅许多。

import React, { ConcurrentMode } from 'react'
import { flushSync } from 'react-dom'

import './index.css'

class Parent extends React.Component {
  state = {
    async: true,
    num: 1,
    length: 20000,
  }

  componentDidMount() {
    this.interval = setInterval(() => {
      this.updateNum()
    }, 200)
  }

  componentWillUnmount() {
    // 别忘了清除interval
    if (this.interval) {
      clearInterval(this.interval)
    }
  }

  updateNum() {
    const newNum = this.state.num === 3 ? 0 : this.state.num + 1
    if (this.state.async) {
      this.setState({
        num: newNum,
      })
    } else {
      flushSync(() => {
        this.setState({
          num: newNum,
        })
      })
    }
  }

  render() {
    const children = []

    const { length, num, async } = this.state

    for (let i = 0; i < length; i++) {
      children.push(
        <div className="item" key={i}>
          {num}
        </div>,
      )
    }

    return (
      <div className="main">
        async:{' '}
        <input
          type="checkbox"
          checked={async}
          onChange={() => flushSync(() => this.setState({ async: !async }))}
        />
        <div className="wrapper">{children}</div>
      </div>
    )
  }
}

export default () => (
  <ConcurrentMode>
    <Parent />
  </ConcurrentMode>
)
@keyframes slide {
  0% {
    margin-left: 0;
    /* transform: translateX(0); */
  }

  50% {
    margin-left: 200px;
    /* transform: translateX(200px); */
  }

  100% {
    margin-left: 0;
    /* transform: translateX(0); */
  }
}

.wrapper {
  width: 400px;
  animation-duration: 3s;
  animation-name: slide;
  animation-iteration-count: infinite;
  display: flex;
  flex-wrap: wrap;
  background: red;
}

.item {
  width: 20px;
  height: 20px;
  line-height: 20px;
  text-align: center;
  border: 1px solid #aaa;
}

React.ConcurrentMode 源码:
没错,源码就是一个 Symbol 符号,显示 react 内部会判断该标记然后做些什么。

React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;

不难想象,经过 createElement() 创建出的 reactElement 树节点应该会是这样:

element = {
  // This tag allows us to uniquely identify this as a React Element
  $$typeof: REACT_ELEMENT_TYPE,

  // Built-in properties that belong on the element
  type: REACT_CONCURRENT_MODE_TYPE, // 看这里
  key: null,
  ref: null,
  props: props,

  // Record the component responsible for creating this element.
  _owner: owner,
};

8. Suspense

Suspense 是一种在组件所依赖的数据尚未加载 ok 时,负责和 react 进行沟通,展示中间态的机制。
react 将会等待数据加载完成然后进行 UI 更新。

传统做法是在组件第一次挂载之后,即 componentDidMount() 或 useEffect() 中加载数据。即:

  1. Start fetching
  2. Finish fetching(然后调用 setState)
  3. Start rendering(再次渲染)

而 Suspense 也是先获取数据,(而且可以比传统做法更早一步,在第一次渲染之前),接着立马就开始第一次渲染(甚至在网络请求被实际发出前),遇到悬而未决即数据尚未获取,则挂起(suspends)该组件,跳过,然后继续渲染 element 树中其他的组件,如果又遇到还没搞定的 Suspense,则继续挂起并跳过。

  1. Start fetching
  2. Start rendering
  3. Finish fetching

Suspense 具体使用时,有一个“边界”概念,只有当一个 Suspense 内部的所有“挂起”都落地后,Suspense 才会停止展示 fallback 中间态,然后一并展示内部的 UI,因此可以通过合理增添 Suspense 边界来控制这种粒度。

// 用于懒加载的组件:
import React from 'react'
export default () => <p>Lazy Comp</p>
import React, { Suspense, lazy } from 'react'

const LazyComp = lazy(() => import('./lazy.js'))

let data = ''
let promise = ''
function requestData() {
  if (data) return data
  if (promise) throw promise
  promise = new Promise(resolve => {
    setTimeout(() => {
      data = 'Data resolved'
      resolve()
    }, 2000)
  })
  throw promise
}

function SuspenseComp() {
  const data = requestData() // 数据尚未加载完成时,该组件的渲染会被挂起。

  return <p>{data}</p>
}

export default () => (
  <Suspense fallback="loading data">
    <SuspenseComp />
    <LazyComp />
  </Suspense>
)

Suspense 源码:

  Suspense: REACT_SUSPENSE_TYPE // 又是一个 symbol 常量

lazy 源码:
_ctor 就是调用 lazy() 时传入的函数,该函数应返回一个 Thenable(具有then方法的对象)。react 渲染到该组件时,会调用 _ctor 函数。
_status 用于记录当前 Thenable 的状态, -1 代表尚未解决,对应到 promise 中也就是 pending 状态。
_result 用于存放 resolve 后的结果。

import type {LazyComponent, Thenable} from 'shared/ReactLazyComponent';

import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';

export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
  return {
    $$typeof: REACT_LAZY_TYPE,
    _ctor: ctor,
    // React uses these fields to store the result.
    _status: -1,
    _result: null, 
  };
}

另外,关于 Suspense 能解决异步竞态问题的理解:

异步请求带来的竞态问题,本质是因为异步请求和 React 分别处于各自的生命周期,二者并未相互同步、一一对应。往往需要等待一段时间,在数据返回之后才去调用 setState,如果快速操作 UI,多次发送相同请求时,由于异步请求时间的不确定性,可能第一条请求反而比第二条同样的请求,响应的更慢,这将导致页面展示了“旧”的数据,带来了混乱。

而使用 Suspense 时,是在UI触发之后,立即调用 setState 尝试去更新数据,如果数据还没返回,则 Suspense 会挂起(suspend)并展现 fallback 中间态。但这个等待正确数据的时间管理工作,已经交给 Suspense 内部去处理了,setState 的使命已经立即完成。暂时理解为脏活累活交给 Suspense 自行消化。

Suspense 结合 useTransition 使用更佳:

This scenario (Receded → Skeleton → Complete) is the default one. However, the Receded state is not very pleasant because it “hides” existing information. This is why React lets us opt into a different sequence (Pending → Skeleton → Complete) with useTransition.
When we useTransition, React will let us “stay” on the previous screen — and show a progress indicator there. We call that a Pending state. It feels much better than the Receded state because none of our existing content disappears, and the page stays interactive.

9. Hooks

hooks 用于 function 函数式组件,内部没有 class component 中那样用来维持内部状态的 this 对象,也没有典型的生命周期方法。

一个非常简单的例子,hooks 是一个大块的内容,这里不表。

/**
 * 必须要react和react-dom 16.7以上
 */

import React, { useState, useEffect } from 'react';

export default () => {
  const [name, setName] = useState('jokcy');

  useEffect(() => {
    console.log('component update');

    return () => {
      console.log('unbind');
    };
  }, []);

  return (
    <>
      <p>My Name is: {name}</p>
      <input type="text" value={name} onChange={e => setName(e.target.value)}/>
    </>
  );
}

useState 源码:
useEffect 等 use* 类似,都是在调用 dispatcher 上的方法。后期会再深入研究 hooks 内层的源码:

export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
// 局部
function resolveDispatcher() {
  const dispatcher = ReactCurrentOwner.currentDispatcher;
  invariant(
    dispatcher !== null,
    'Hooks can only be called inside the body of a function component.',
  );
  return dispatcher;
}
/**
 * Keeps track of the current owner.
 *
 * The current owner is the component who should own any components that are
 * currently being constructed.
 */
const ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Fiber), // 代表当前正在被构建的组件实例
  currentDispatcher: (null: null | Dispatcher),
};

export default ReactCurrentOwner;

10. Children

Children 上的方法有:

Children: {
  map,
  forEach,
  count,
  toArray,
  only,
},

React.Children.map() 中 map 可以遍历 child 并映射展开:

import React from 'react'

function ChildrenDemo(props) {
  console.log(props.children)
  console.log(React.Children.map(props.children, c => [c, [c, c]]))
  return props.children
}

export default () => (
  <ChildrenDemo>
    <span>1</span>
    <span>2</span>
  </ChildrenDemo>
)

map 遍历 children 时有个 contextPool 对象常量池的概念,用于复用对象。以节省递归遍历 child 时多次对象创建和 GC 回收的开销。

React 这么实现主要是两个目的:

  1. 拆分 map 出来的数组
  2. 因为对 Children 的处理一般在 render 里面,所以会比较频繁,所以设置一个对象池子减少声明和 GC 的开销。


    image

11. Other

  • memo ,用于 function component,功能上对标 class component 中的 pureComponent

浅层源码中,也没多少东西,只是返回了一个带有 $$typeof 标记的对象及 typecompare ,memo 这种返回类似前面 createRef 的返回:

export default function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  if (__DEV__) {
    if (!isValidElementType(type)) {
      warningWithoutStack(
        false,
        'memo: The first argument must be a component. Instead ' +
          'received: %s',
        type === null ? 'null' : typeof type,
      );
    }
  }
  return {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
}
  • FragElement(简写方式是空标签 <></>

最外层源码就是 FragElement: REACT_FRAGMENT_TYPE
是临时节点,因为 React 要求不能直接返回多个兄弟节点,要么“包”一层,要么返回数组。而 FragElement 就是用来“包”一层的,相比于用真实的 div 去“包”一层,FragElement 并不会实际被创建。

  • cloneElement

克隆一个节点,源码基本和 createElement 一样,只是第一个参数从 type 变为了 element ,实际即要克隆的 element 对象上属性,然后把参数丢给 ReactElement() 来返回一个新的 element 对象:

/**
 * Clone and return a new ReactElement using element as the starting point.
 * See https://reactjs.org/docs/react-api.html#cloneelement
 */
export function cloneElement(element, config, children) {
  invariant(
    !(element === null || element === undefined),
    'React.cloneElement(...): The argument must be a React element, but you passed %s.',
    element,
  );

  let propName;

  // Original props are copied
  const props = Object.assign({}, element.props);

  // Reserved names are extracted
  let key = element.key;
  let ref = element.ref;
  // Self is preserved since the owner is preserved.
  const self = element._self;
  // Source is preserved since cloneElement is unlikely to be targeted by a
  // transpiler, and the original source is probably a better indicator of the
  // true owner.
  const source = element._source;

  // Owner will be preserved, unless ref is overridden
  let owner = element._owner;

  if (config != null) {
    if (hasValidRef(config)) {
      // Silently steal the ref from the parent.
      ref = config.ref;
      owner = ReactCurrentOwner.current;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    // Remaining properties override existing props
    let defaultProps;
    if (element.type && element.type.defaultProps) {
      defaultProps = element.type.defaultProps;
    }
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        if (config[propName] === undefined && defaultProps !== undefined) {
          // Resolve default props
          props[propName] = defaultProps[propName];
        } else {
          props[propName] = config[propName];
        }
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  return ReactElement(element.type, key, ref, self, source, owner, props);
}

  • createFactory

如果时候 jsx 语法,而不是手动用 JS 调用 createElement 来创建 element 树的话,基本不会用到该方法,createFactory 就只是包了一层,省的每次创建同样类型的 element,都传入第一个 type 参数了:

/**
 * Return a function that produces ReactElements of a given type.
 * See https://reactjs.org/docs/react-api.html#createfactory
 */
export function createFactory(type) {
  const factory = createElement.bind(null, type);
  // Expose the type on the factory and the prototype so that it can be
  // easily accessed on elements. E.g. `<Foo />.type === Foo`.
  // This should not be named `constructor` since this may not be the function
  // that created the element, and it may not even be a constructor.
  // Legacy hook: remove it
  factory.type = type;
  return factory;
}

  • isValidElement

判断一个对象是否是合法的 element:

/**
 * Verifies the object is a ReactElement.
 * See https://reactjs.org/docs/react-api.html#isvalidelement
 * @param {?object} object
 * @return {boolean} True if `object` is a ReactElement.
 * @final
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265