快来跟我一起学 React(Day7)

简介

我们继续上一节的内容,开始分析 React 官网:https://reactjs.org/docs/accessibility.html 的 “高级指引” 部分,这一部分会涉及到 Refs 转发、Fragments、高阶组件等概念的分析,比前面章节的难度还是略微大一些的,所以一定要跟上节奏哦,我们一起出发吧!

知识点

  • Refs 转发
  • Fragments
  • 高阶组件
  • 深入 JSX

准备

我们直接用上一节中的 react-demo-day5 项目来作为我们的 Demo 项目,还没有创建的小伙伴可以直接执行以下命令 clone 一份代码:

git clone -b dev https://gitee.com/vv_bug/react-demo-day5.git

接着进入到项目根目录 react-demo-day5 ,并执行以下命令来安装依赖与启动项目:

npm install --registry https://registry.npm.taobao.org && npm start
1-1.png

等项目打包编译成功,浏览器会自动打开项目入口,看到上面截图的效果的时候,我们的准备工作就完成了。

https://gitee.com/vv_bug/react-demo-day5/tree/dev)

Refs 转发

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。

解释起来有点抽象,我们还是用 Demo 来演示一下。

转发 refs 到 DOM 组件

因为上一节测试 “错误边界” 组件的时候抛了一个错误, src/advanced-guides 目录下的模块都变成了 “Something went wrong”,所以我们先修改一下 src/advanced-guides/error.tsx 组件,让它不要再报错了:

function ErrorCom(): null{
  return null;
  // throw new Error("报错啦!");
}
export default ErrorCom;

我们在 src/advanced-guides 目录下创建一个 forward-ref 目录:

mkdir ./src/advanced-guides/forward-ref

然后我们在 src/advanced-guides/forward-ref 目录下创建一个 index.tsx 文件:

import React from "react";
import CusInput from "./cus-input";

function ForwardRef() {
  let cusInputRef: any;
  const handleInputRef = (ref: any) => {
    cusInputRef = ref;
  };
    /*
        让 input 元素聚焦
    */
  function focusInput() {
    cusInputRef && cusInputRef.focus();
  }

  return (
    <React.Fragment>
      {/* 自定义 input 组件  */ }
      <CusInput ref={ handleInputRef }/>
      <button onClick={ focusInput }>聚焦 input</button>
    </React.Fragment>
  );
}

export default ForwardRef;

可以看到,我们自定义了一个 CusInput,然后获取了 CusInput 元素的引用 ref,最后通过 ref 让自定义的 CusInput 元素自动获取焦点。

ok,然后我们在 src/advanced-guides/forward-ref 目录下创建一个 cus-input.tsx 组件:

import React from "react";
function CusInput(props:any, ref: any) {
  return (
    <div>
      <input ref={ref}/>
    </div>
  );
}
export default React.forwardRef(CusInput);

可以看到,我们定义了一个函数式组件 CusInput,并且通过 React.forwardRef 方法把 CusInput 组件的 ref 指向了其子元素 input

最后我们 src/advanced-guides/index.tsx 组件中引入 src/advanced-guides/forward-ref/index.tsx 组件:

/**
 * 核心概念列表
 */
import CodeSplit from "./code-split";
import Context from "./context";
import ErrorBoundaries from "./error-boundaries";
import ErrorCom from "./error";
import ForwardRef from "./forward-ref";

function AdvancedGuides() {
  return (
    <ErrorBoundaries>
      <div>
        {/* 代码分割 */ }
        <CodeSplit/>
        {/* Context */ }
        <Context/>
        {/* 报错的组件 */ }
        <ErrorCom/>
        {/* Refs 转发 */ }
        <ForwardRef/>
      </div>
    </ErrorBoundaries>
  );
};
export default AdvancedGuides;

我们重新运行项目看结果:

npm start
1-2.gif

可以看到,当我们点击 “聚焦 input” 按钮的时候,input 元素自动被聚焦了。

接下来我们用类组件的形式来实现一下 src/advanced-guides/forward-ref/cus-input.tsx 组件。

我们在 src/advanced-guides/forward-ref 目录下创建一个 cus-input.com.tsx 文件:

import React from "react";
import PropTypes from "prop-types";

type Prop = {
  handleRef: (ref: any) => void
};

class CusInputCom extends React.Component<Prop> {
  static propTypes = {
    handleRef: PropTypes.func
  }

  render() {
    return (
      <div>
        <input ref={ this.props.handleRef }/>
      </div>
    );
  }
}

export default React.forwardRef((props, ref: any) => {
  return <CusInputCom { ...props } handleRef={ ref }/>;
});

然后在 src/advanced-guides/forward-ref/index.tsx 组件中引入 src/advanced-guides/forward-ref/cus-input.com.tsx 组件:

import React from "react";
import CusInput from "./cus-input";
import CusInputCom from "./cus-input.com";

function ForwardRef() {
  let cusInputRef: any;
  let cusInputRef2: any;
  const handleInputRef = (ref: any) => {
    cusInputRef = ref;
  };
  const handleInputRef2 = (ref: any) => {
    cusInputRef2 = ref;
  };

  function focusInput() {
    cusInputRef && cusInputRef.focus();
  }
  function focusInput2() {
    cusInputRef2 && cusInputRef2.focus();
  }

  return (
    <React.Fragment>
      {/* 自定义 input 组件  */ }
      <CusInput ref={ handleInputRef }/>
      {/* 自定义 input 组件  */ }
      <CusInputCom ref={ handleInputRef2 }/>
      <button onClick={ focusInput }>聚焦 input</button>
      <button onClick={ focusInput2 }>聚焦 input2</button>
    </React.Fragment>
  );
}

export default ForwardRef;

我们重新运行项目看结果:

npm start
1-3.gif

可以看到,我们分别用 “函数组件”、“类组件” 实现了 CusInput 组件。

React.createRef

我们上面用的都是使用了一个方法去接受组件的 ref 属性:

let cusInputRef: any;
const handleInputRef = (ref: any) => {
  cusInputRef = ref;
}; 
{/* 自定义 input 组件  */ }
 <CusInput ref={ handleInputRef }/>

其实接受一个组件的 ref 属性,除了利用函数外,我们还可以利用 React 提供的 createRef 方法。

我们来改造一下 src/advanced-guides/forward-ref/index.tsx 组件:

import React from "react";
import CusInput from "./cus-input";
import CusInputCom from "./cus-input.com";

function ForwardRef() {
  // let cusInputRef: any;
  // let cusInputRef2: any;
  let cusInputRef = React.createRef<HTMLInputElement>();
  let cusInputRef2 = React.createRef<HTMLInputElement>();
  function focusInput() {
    cusInputRef?.current?.focus();
  }
  function focusInput2() {
    cusInputRef2?.current?.focus();
  }

  return (
    <React.Fragment>
      {/* 自定义 input 组件  */ }
      <CusInput ref={ cusInputRef }/>
      {/* 自定义 input 组件  */ }
      <CusInputCom ref={ cusInputRef2 }/>
      <button onClick={ focusInput }>聚焦 input</button>
      <button onClick={ focusInput2 }>聚焦 input2</button>
    </React.Fragment>
  );
}

export default ForwardRef;

我们还需要简单的修改一下 src/advanced-guides/forward-ref/cus-input.com.tsx 组件:

import React from "react";
import PropTypes from "prop-types";

type Prop = {
  handleRef: React.RefObject<HTMLInputElement>
};

class CusInputCom extends React.Component<Prop> {
  static propTypes = {
    handleRef: PropTypes.object
  }
  render() {
    return (
      <div>
        <input ref={ this.props.handleRef }/>
      </div>
    );
  }
}

export default React.forwardRef((props, ref: any) => {
  return <CusInputCom { ...props } handleRef={ ref }/>;
});

可以看到,我们把之前的函数接受 ref 全改成了 React.createRef<HTMLInputElement>() 方式:

let cusInputRef = React.createRef<HTMLInputElement>();
  let cusInputRef2 = React.createRef<HTMLInputElement>();

React 会自动把 ref 挂载到传入对象的 current 属性中:

 function focusInput() {
    cusInputRef?.current?.focus();
  }
  function focusInput2() {
    cusInputRef2?.current?.focus();
  }

效果跟上面一样,我就不演示了。

欢迎志同道合的小伙伴一起交流,一起学习。

Fragments

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

其实我们在 Demo 中已经使用过 Fragment 组件了,比如我们的 src/advanced-guides/forward-ref/index.tsx 组件:

return (
    <React.Fragment>
      {/* 自定义 input 组件  */ }
      <CusInput ref={ cusInputRef }/>
      {/* 自定义 input 组件  */ }
      <CusInputCom ref={ cusInputRef2 }/>
      <button onClick={ focusInput }>聚焦 input</button>
      <button onClick={ focusInput2 }>聚焦 input2</button>
    </React.Fragment>
  );

我们可以试着把 src/advanced-guides/forward-ref/index.tsx 组件中的 React.Fragment 组件去掉:

1-4.png

可以看到,IDE 就直接报错了,说 “JSX 语法必须包含一个父元素”。

有童鞋要说了,“我们可以直接定一个 div 元素或者其它元素呀”,是的!你可以这样做,但是当我们定义的是 div 元素的时候,最后是会被渲染到 DOM 中的,而我们想要的是不需要渲染到 DOM 中,所以我们就可以使用 Fragment 组件。

短语法

你可以使用一种新的,且更简短的语法来声明 Fragments。它看起来像空标签:

return (
    <>
      {/* 自定义 input 组件  */ }
      <CusInput ref={ cusInputRef }/>
      {/* 自定义 input 组件  */ }
      <CusInputCom ref={ cusInputRef2 }/>
      <button onClick={ focusInput }>聚焦 input</button>
      <button onClick={ focusInput2 }>聚焦 input2</button>
    </>
  );

带 key 的 Fragments

使用显式 React.Fragment 语法声明的片段可能具有 key。一个使用场景是将一个集合映射到一个 Fragments 数组 - 举个例子,创建一个描述列表:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有`key`,React 会发出一个关键警告
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

key 是唯一可以传递给 Fragment 组件的属性。

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

使用 HOC 解决横切关注点问题

组件是 React 中代码复用的基本单元。但你会发现某些模式并不适合传统组件。

例如我们之前的切换主题组件 src/advanced-guides/context/context.func.tsx ,如果我们需要获取到 Context 对象中的 themetoggleTheme,我们需要这样做:

import {AppContext} from "../../app-context";
import React from "react";

function ContextFunc() {
  return (
    <div>
      <AppContext.Consumer>
        { ({toggleTheme}) => <button onClick={ toggleTheme }>点我切换主题</button> }
      </AppContext.Consumer>
    </div>
  );
}

export default ContextFunc;

我们需要利用 AppContext.Consumer 方式,或者类组件中的静态 contextType 属性方式来获取到 Context 对象中的数据。

小伙伴有没有想过,我们的自定义组件需要关心 Context 对象怎么获取吗?能不能有一种方式直接把 Context 中的数据直接通过 props 传递给我呢?我每次写一个组件为了去获取 Context 还得去写这么多代码。

ok,“高阶组件” 来了!

我们首先在 src/advanced-guides 目录下创建一个 hoc 目录:

mkdir ./src/advanced-guides/hoc

然后在 src/advanced-guides/hoc 目录下创建一个高级组件 with-theme.tsx 组件:

touch ./src/advanced-guides/hoc/with-theme.tsx

然后将以下内容写入到 src/advanced-guides/hoc/with-theme.tsx 组件:

import React from "react";
import {AppContext, AppContextType, Themes} from "../../app-context";

export type ThemeType = {
    theme: Themes,
    toggleTheme: () => void
};
export type getThemeDataType = {
    (appContext: AppContextType): ThemeType;
};

/**
 * 带主题的高阶组件
 * @param getThemeData
 */
function withTheme(getThemeData: getThemeDataType) {
    return function (WrappedComponent: typeof React.Component | React.FunctionComponent) {
        // 转发 ref 函数组件
        const RefComponent = (props: any, ref: any) => {
            class ThemeComponent extends React.Component {
                render() {
                    return (
                        <AppContext.Consumer>
                            {(appContext) => (
                                <WrappedComponent
                                    ref={ref}
                                    {...this.props}
                                    {...getThemeData(appContext)}
                                />
                            )}
                        </AppContext.Consumer>
                    );
                };
            }

            return (
                <ThemeComponent
                    {...props}
                />
            );
        };
        // 转发 ref
        return React.forwardRef(RefComponent);
    };
}

export default withTheme;

这里我们做了几步工作:

  1. 定义了一个带主题的高阶组件 withTheme 函数。
  2. 定义了一个转发 ref 函数组件 RefComponent
  3. 定义了一个带主题的组件 ThemeComponent
  4. 利用 AppContext.Consumer 获取到了 AppContext 对象中的数据。

这已经算是一个比较复杂的高阶组件了,因为里面还包含了高阶组件的 ref 转发等功能(算是对前面 forwad-ref 内容的复习了,不熟悉的童鞋记得去看一下前面的文章哦 )。

接着我们修改一下 src/advanced-guides/context/context.com.tsx 组件:

import React from "react";
import withTheme,{ThemeType} from "../hoc/with-theme";
type Prop =ThemeType & {
};
class ContextCom extends React.Component<Prop> {
  render() {
    return (
      <div>
        <button onClick={ this.props.toggleTheme }>点我切换主题</button>
      </div>
    );
  }
}
// 构造一个带主题功能的组件
export default withTheme((appContext) => appContext)(ContextCom);

同样修改一下 src/advanced-guides/context/context.func.tsx 组件:

import React from "react";
import withTheme from "../hoc/with-theme";

function ContextFunc(props: any) {
    return (
        <div>
            <button onClick={props.toggleTheme}>点我切换主题</button>
        </div>
    );
}
// 构造一个带主题功能的组件
export default withTheme((appContext) => appContext)(ContextFunc);

可以看到,是不是变得很简答了呢?我们不需要再考虑 “AppContext 该怎么获取”了,我们只需要利用 withTheme 高阶函数,它就会自动的把 AppContext 中跟主题相关的数据给到这个组件,我们直接在组件中通过 Props 就可以访问了。

1-5.gif

可以看到,效果跟我们之前的一样。

高阶组件的约定

  1. 不要改变原始组件,高级组件应该是一个纯函数。

    ...
    /**
     * 带主题的高阶组件
     * @param getThemeData
     */
    function withTheme(getThemeData: getThemeDataType) {
        return function (WrappedComponent: typeof React.Component | React.FunctionComponent) {
            // 转发 ref 函数组件
            const RefComponent = (props: any, ref: any) => {
                class ThemeComponent extends React.Component {
                    render() {
                        return (
                            <AppContext.Consumer>
                                {(appContext) => (
                                    <WrappedComponent
                                        ref={ref}
                                        {...this.props}
                                        {...getThemeData(appContext)}
                                    />
                                )}
                            </AppContext.Consumer>
                        );
                    };
                }
    
                return (
                    <ThemeComponent
                        {...props}
                    />
                );
            };
            // 转发 ref
            return React.forwardRef(RefComponent);
        };
    }
    
    export default withTheme;
    

    可以看到,我们并没有对传入的 WrappedComponent 组件做任何额外的操作。

  2. 将不相关的 props 传递给被包裹的组件。

     <WrappedComponent
       ref={ref}
       {...this.props}
       {...getThemeData(appContext)}
       />
    

    可以看到,我们保留了 WrappedComponent 组件自身的 props,只是额外通过 getThemeData 方法添加了一些参数。

  3. 最大化可组合性。

    我们定义的 withTheme 是一个返回高阶组件的函数,可以接受 getThemeData 参数,提供给使用者自定义 props 里面的内容。

  4. 约定:包装显示名称以便轻松调试。

    我们直接定义了一个叫 ThemeComponent 的组件,方便调试。

深入 JSX

实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。如下 JSX 代码:

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

会编译为:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

如果没有子节点,你还可以使用自闭合的标签形式,如:

<div className="sidebar" />

会编译为:

React.createElement(
  'div',
  {className: 'sidebar'}
)

指定 React 元素类型

JSX 标签的第一部分指定了 React 元素的类型。

大写字母开头的 JSX 标签意味着它们是 React 组件。这些标签会被编译为对命名变量的直接引用,所以,当你使用 JSX 表达式时,Foo 必须包含在作用域内。

React 必须在作用域内

由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。

例如,在如下代码中,虽然 ReactCustomButton 并没有被直接使用,但还是需要导入:

import React from 'react';
import CustomButton from './CustomButton';
function WarningButton() {
 // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

如果你不使用 JavaScript 打包工具而是直接通过 <script> 标签加载 React,则必须将 React 挂载到全局变量中。

但是在我们的 react-demo-day5 项目中,我们在使用 JSX 的时候可以不用引入 React,比如我们的 src/main-concepts/components-and-props/welcome.func.tsx 组件:

import PropTypes from "prop-types";
type Prop = {
    readonly name: string, // 姓名
};
function Welcome(props: Prop) {
    return <h1>我是函数式组件,Hello, {props.name}</h1>;
}
Welcome.propTypes={
    name: PropTypes.string
};
Welcome.defaultProps = {
    name: "小虫"
};
export default Welcome;

可以看到,我们用了 JSX 语法,但是并没有引入 React,为什么还能正常运行呢?

因为在 React17+ 版本后,React 已经把 JSX 生成 React 元素节点的 API 全部提取到了 react-jsx-xxx.js 文件中去了,那有小伙伴疑问了: “我们也并没有引入 react-jsx-xxx.js 文件呀”。

其实在我们项目中,我们使用了 React 官方提供的 babel-loader 插件集合,我们可以找到 babel.config.js 文件:

module.exports = {
    presets: [
        [
            "babel-preset-react-app", // 添加 react-app 插件集合
            {
                runtime: require.resolve("react/jsx-runtime") ? "automatic" : "classic"
            }
        ],
    ]
};

我们做了判断,当项目中有 react/jsx-runtime 模块的时候,就使用 react/jsx-runtime,没有就使用之前 React 中的 API 方式创建 JSX 元素节点。

我们可以试一下,修改一下 react-demo-day5/babel.config.js 文件:

module.exports = {
    presets: [
        [
            "babel-preset-react-app", // 添加 react-app 插件集合
            {
                runtime: "classic"
            }
        ],
    ]
};

可以看到,我们给 runtime 传递了一个 classic 参数,告诉 babel,在解析 JSX 语法创建节点的时候使用 ReactAPI

我们重新运行一下项目:

npm start
1-6.png

可以看到,项目运行的时候直接报错了,说找不到 React 变量。

我们还是把 babel.config.js 改回来吧:

module.exports = {
    presets: [
        [
            "babel-preset-react-app", // 添加 react-app 插件集合
            {
                runtime: require.resolve("react/jsx-runtime") ? "automatic" : "classic"
            }
        ],
    ]
};

我们再次重新运行项目:

npm start

然后我们看一下经过 babel-preset-react-app 插件处理过后的 src/main-concepts/components-and-props/welcome.func.tsx 组件:

1-7.png
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! prop-types */ "./node_modules/prop-types/index.js");-demo-day5/src/main-concepts/components-and-props/welcome.func.tsx";



function Welcome(props) {
  return /*#__PURE__*/(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_1__.jsxDEV)("h1", {
    children: ["\u6211\u662F\u51FD\u6570\u5F0F\u7EC4\u4EF6\uFF0CHello, ", props.name]
  }, void 0, true, {
    fileName: _jsxFileName,
    lineNumber: 6,
    columnNumber: 12
  }, this);
}

Welcome.propTypes = {
  name: (prop_types__WEBPACK_IMPORTED_MODULE_0___default().string)
};
Welcome.defaultProps = {
  name: "小虫"
};
const __WEBPACK_DEFAULT_EXPORT__ = (Welcome);

可以看到, babel-preset-react-app 插件会自动的引入 react/jsx-dev-runtime.js 模块,并且利用 react/jsx-dev-runtime.js 模块的 jsxDEV 方法创建一个 React 元素。

在 JSX 类型中使用点语法

在 JSX 中,你也可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,这会非常方便。例如,如果 MyComponents.DatePicker 是一个组件,你可以在 JSX 中直接使用:

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;}

用户定义的组件必须以大写字母开头

以小写字母开头的元素代表一个 HTML 内置组件,比如 或者 会生成相应的字符串 'div' 或者 'span' 传递给 React.createElement(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 <Foo /> 会编译为 React.createElement(Foo)

建议使用大写字母开头命名自定义组件。如果你确实需要一个以小写字母开头的组件,则在 JSX 中使用它之前,必须将它赋值给一个大写字母开头的变量。

总结

这一节我们介绍了 Refs 转发、Fragments、高级组件、深入 JSX等知识点,可能有些小伙伴要说了:“我项目从头到尾就没用到什么高级组件、Refs 转发”,是的!也并不是所有场景都需要用到这些高级技巧,比如你把你所有的组件逻辑都抽离到了 “高阶组件” 中,这样高阶组件就会变得十分臃肿,甚至还会造成性能问题,这样就有点得不偿失了,具体还得跟自己项目实际情况来使用,就像我们 Demo 中的换肤功能、我们使用了高阶组件来处理,可以帮我们省了很多隐藏的工作,使每个组件逻辑变得更清晰,你只需简单的调用一个方法,就可以具备有换肤功能了,小伙伴不用慌张,当你在项目中不断总结跟磨练,最后具备一定经验的时候,你自然而然就懂了。

好啦,这节到这就结束啦。

Demo 项目全部代码:https://gitee.com/vv_bug/react-demo-day5/tree/dev/

/* harmony import / var prop_types__WEBPACK_IMPORTED_MODULE_0___default = /#PURE/webpack_require.n(prop_types__WEBPACK_IMPORTED_MODULE_0__);
/
harmony import / var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_1__ = webpack_require(/! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js");
var _jsxFileName = "/xxx/react
觉得写得不错的可以点点关注,帮忙转发跟点赞。

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

推荐阅读更多精彩内容