React Hooks

使用React Hooks有什么优势?

什么是hooks
hook 是一些可以让你在函数组件里面钩入react state 以及生命周期的特定的函数。

  • Hooks 本质是把面向生命周期编程变成了面向业务逻辑编程;
  • Hooks 使用上是一个逻辑状态盒子,输入输出表示的是一种联系;
  • Hooks 是 React 的未来,但还是无法完全替代原始的 Class。
  • 每个 Hook 都为Function Component提供使用 React 状态和生命周期特性的通道。Hooks 不能在Class Component中使用。

疑问

    1. 为什么只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用?
    1. 为什么 useEffect 第二个参数是空数组,就相当于 ComponentDidMount ,只会执行一次?
    1. 自定义的 Hook 是如何影响使用它的函数组件的?
    1. Capture Value 特性是如何产生的?

class 组件

主要问题

  • 在hooks出来之前,常见的代码重用方式是HOCs和render props,这两种方式带来的问题是:你需要解构自己的组件,非常的笨重,同时会带来很深的组件嵌套
  • 复杂的组件逻辑:复杂的业务逻辑里面存在各种生命周期,导致代码拆分比较困难,很难复用
  • 难理解的class 组件

hooks 的到来:

带组件状态的逻辑很难重用:

  • class 组件复用主要通过引入render props或higher-order components这样的设计模式。如react-redux提供的connect方法。这种方案不够直观,而且需要改变组件的层级结构,极端情况下会有多个wrapper嵌套调用的情况。

  • Hooks可以在不改变组件层级关系的前提下,方便的重用带状态的逻辑。也可以自己定义状态组件

复杂组件难于理解:

  • 大量的业务逻辑需要放在componentDidMount和componentDidUpdate等生命周期函数中,而且往往一个生命周期函数中会包含多个不相关的业务逻辑,如日志记录和数据请求会同时放在componentDidMount中。另一方面,相关的业务逻辑也有可能会放在不同的生命周期函数中,如组件挂载的时候订阅事件,卸载的时候取消订阅,就需要同时在componentDidMount和componentWillUnmount中写相关逻辑。

  • Hooks可以封装相关联的业务逻辑,让代码结构更加清晰。

难于理解的 Class 组件:

  • JS 中的this关键字让不少人吃过苦头,它的取值与其它面向对象语言都不一样,是在运行时决定的

  • Hooks可以在不引入 Class 的前提下,使用 React 的各种特性。

class RandomUserModal extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: {},
      loading: false,
    };
    this.fetchData = this.fetchData.bind(this);
  }

  componentDidMount() {
    if (this.props.visible) {
      this.fetchData();
    }
  }

  componentDidUpdate(prevProps) {
    if (!prevProps.visible && this.props.visible) {
      this.fetchData();
    }
  }
// 获取数据
  fetchData() {
// 打开loading
    this.setState({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(res => res.json())
      .then(json => this.setState({
        user: json.results[0],
        loading: false,
      }));
  }

  render() {
    const user = this.state.user;
    return (
      <ReactModal
        isOpen={this.props.visible}
      >
        <button onClick={this.props.handleCloseModal}>Close Modal</button>
        {this.state.loading ?
          <div>loading...</div>
          :
          <ul>
            <li>Name: {`${(user.name || {}).first} ${(user.name || {}).last}`}</li>
            <li>Gender: {user.gender}</li>
            <li>Phone: {user.phone}</li>
          </ul>
        }
      </ReactModal>
    )
  }
}

该 Modal 的展示与否由父组件控制,因此会传入参数 visible 和 handleCloseModal(用于 Modal 关闭自己)。 实现在 Modal 打开的时候才进行数据获取,我们需要同时在 componentDidMount 和 componentDidUpdate 两个生命周期里实现数据获取的逻辑

我们需要将其按照 React 组件生命周期进行拆解。这种拆解除了代码冗余,还很难复用。

Hooks 写法:

function RandomUserModal(props) {
  const [user, setUser] = React.useState({});
  const [loading, setLoading] = React.useState(false);

  React.useEffect(() => {
    if (!props.visible) return;
    setLoading(true);
    fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
      setUser(json.results[0]);
      setLoading(false);
    });
  }, [props.visible]);
  
  return (
    // View 部分几乎与上面相同
  );
}

优势是代码精简, 可以通过 自定义 的hook 将 重要的逻辑抽离出去

// 自定义 Hook
function useFetchUser(visible) {
  const [user, setUser] = React.useState({});
  const [loading, setLoading] = React.useState(false);
  
  React.useEffect(() => {
    if (!visible) return;
    setLoading(true);
    fetch('https://randomuser.me/api/').then(res => res.json()).then(json => {
      setUser(json.results[0]);
      setLoading(false);
    });
  }, [visible]);
  return { user, loading };
}

function RandomUserModal(props) {
  const { user, loading } = useFetchUser(props.visible);
  
  return (
    // 与上面相同
  );
}

useState

useState 是一个hook,它的入参是state 的初始值,返回一个数组,包含当前state 和 用于更改 state 的函数

  • class 组件有一个大的state 对象,通过this.setState 一次改变整个state对象
  • 函数组件根本没有状态,但useState hook允许我们在需要时添加很小的状态块

React有能力在调用每个组件之前做一些设置,这就是它设置这个状态的时候。
其中做的一件事设置 Hooks 数组。 它开始是空的, 每次调用一个hook时,React 都会向该数组添加该 hook。

假如有这样一个函数

function AudioPlayer() {
  const [volume, setVolume] = useState(80);
  const [position, setPosition] = useState(0);
  const [isPlaying, setPlaying] = useState(false);

  .....
}

因为它调用useState 3次,React 会在第一次渲染时将这三个 hook 放入 Hooks 数组中。

下次渲染时,同样的3个hooks以相同的顺序被调用,所以React可以查看它的数组,并发现已经在位置0有一个useState hook ,所以React不会创建一个新状态,而是返回现有状态。

  • 1、React 创建组件时,它还没有调用函数。React 创建元数据对象和Hooks的空数组。假设这个对象有一个名为nextHook的属性,它被放到索引为0的位置上,运行的第一个hook将占用位置0。

  • 2、React 调用你的组件(这意味着它知道存储hooks的元数据对象)。

  • 3、调用useState,React创建一个新的状态,将它放在hooks数组的第0位,返回[volume,setVolume]对,并将volume 设置为其初始值80,它还将nextHook索引递增1。

  • 4、再次调用useState,React查看数组的第1位,看到它是空的,并创建一个新的状态。 然后它将nextHook索引递增为2,并返回 [position,setPosition]

  • 5、第三次调用useState。 React看到位置2为空,同样创建新状态,将nextHook递增到3,并返回[isPlaying,setPlaying]

useEffect

你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

默认情况下,它在第一次渲染之后和每次更新之后都会执行。

useEffect做了什么

引用官方文档的例子:

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。

  • 在渲染的时候被创建,在浏览器绘制之后运行。
  • 如果给出了销毁指令,它们将在下一次绘制前被销毁。
  • 它们会按照定义的顺序被运行。

渲染函数只是创建了 fiber 节点,但是并没有绘制任何内容。

通常来说,应该是 fiber 保存包含了 effect 节点的队列。每个 effect 节点都是一个不同的类型,并能在适当的状态下被定位到:

hook effect 将会被保存在 fiber 一个称为 updateQueue 的属性上,每个 effect 节点都有如下的结构(详见源码):

type Effect = {
  tag: HookEffectTag,  // 它控制了 effect 节点的行为
  create: () => mixed,  // 绘制之后运行的回调函数
  destroy: (() => mixed) | null,
  inputs: Array<mixed>,  // 一个集合,该集合中的值将会决定一个 effect 节点是否应该被销毁或者重新创建。
  next: Effect,   //它指向下一个定义在函数组件中的 effect 节点
};

export type HookEffectTag = number;

export const NoEffect = /*             */ 0b00000000;
export const UnmountSnapshot = /*      */ 0b00000010;
export const UnmountMutation = /*      */ 0b00000100;
export const MountMutation = /*        */ 0b00001000;
export const UnmountLayout = /*        */ 0b00010000;
export const MountLayout = /*          */ 0b00100000;
export const MountPassive = /*         */ 0b01000000;
export const UnmountPassive = /*       */ 0b10000000;
// 这个 tag 属性值是由二进制的值组合而成

React 提供了一些特殊的 effect hook:比如 useMutationEffect() 和 useLayoutEffect()。这两个 effect hook 内部都使用了 useEffect(),实际上这就意味着它们创建了 effect hook,但是却使用了不同的 tag 属性值。

react 又是如何检查处罚的呢?

do {
      if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = null;
        if (destroy !== null) {
          destroy();
        }
      }
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;
        let destroy = create();
        if (typeof destroy !== 'function') {
          if (__DEV__) {
            if (destroy !== null && destroy !== undefined) {
              warningWithoutStack(
                false,
                'useEffect function must return a cleanup function or ' +
                  'nothing.%s%s',
                typeof destroy.then === 'function'
                  ? ' Promises and useEffect(async () => ...) are not ' +
                    'supported, but you can call an async function inside an ' +
                    'effect.'
                  : '',
                getStackByFiberInDevAndProd(finishedWork),
              );
            }
          }
          destroy = null;
        }
        effect.destroy = destroy;
      }
      effect = effect.next;
    }

React 组件中有两种常见的副作用:
需要清理的副作用
不需要清理的副作用。

无需清除的effect:

只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。

需要清除的effect:

例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

使用class

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

你通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它。
使用生命周期函数迫使我们拆分这些逻辑代码,即使这两部分代码都作用于相同的副作用

使用 Hook 的示例:

function FriendStatus(props) {
  // ...
  useEffect(() => {
    // ...
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分

多个effect:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。
它会在调用一个新的 effect 之前对前一个 effect 进行清理


// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 运行第一个 effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 运行下一个 effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 运行下一个 effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect

effect优化

每次渲染后都执行清理或者执行 effect 可能会导致性能问题

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅
只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useReducer:

useState的替代方案。接受类型为(state,action)=> newState的reducer,并返回与dispatch方法配对的当前状态

const [state, dispatch] = useReducer(reducer, initialArg, init);
有两种不同初始化 useReducer state 的方式

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

初始值也可以延迟初始化, useReducer(reducer, initialCount, init), init 是一个函数, 初始值将设置为init(initialArg)
如果从Reducer Hook返回与当前状态相同的值,则React将退出而不渲染子项或触发效果。

useCallback

useCallback将返回一个回调的memoized(一种优化手段,遇到计算开销很大的函数时,会缓存其计算结果,下次同样的输入就可以直接返回缓存的结果)版本,该版本仅在其中一个依赖项发生更改时才会更改。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染
然而,useRef()ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式

useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。

在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染。

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

export default function App(props){
  const [count, setCount] = useState(0);

  const doubleCount = useMemo(() => {
    return 2 * count;
  }, [count]);

  const timerID = useRef();
  
  useEffect(() => {
    timerID.current = setInterval(()=>{
        setCount(count => count + 1);
    }, 1000); 
  }, []);
  
  useEffect(()=>{
      if(count > 10){
          clearInterval(timerID.current);
      }
  });
  // 用ref对象的current属性来存储定时器的ID
  return (
    <>
      <button ref={couterRef} onClick={() => {setCount(count + 1)}}>Count: {count}, double: {doubleCount}</button>
    </>
  );
}

为什么只能在函数最外层调用 Hook,不要在循环、条件判断或者子函数中调用?

function ExampleWithManyStates() {
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
// useState无论调用多少次,相互之间是独立的

react是根据useState出现的顺序来定的

//第一次渲染
useState(42);  //将age初始化为42
useState('banana');  //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...

//第二次渲染
useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
useState('banana');  //读取状态变量fruit的值(这时候传的参数banana直接被忽略)
useState([{ text: 'Learn Hooks' }]); //...

如果放在循环或者判断里面

let showFruit = true;
function ExampleWithManyStates() {
  const [age, setAge] = useState(42);

  if(showFruit) {
    const [fruit, setFruit] = useState('banana');
    showFruit = false;
  }

  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}

//第一次渲染
useState(42);  //将age初始化为42
useState('banana');  //将fruit初始化为banana
useState([{ text: 'Learn Hooks' }]); //...

//第二次渲染
useState(42);  //读取状态变量age的值(这时候传的参数42直接被忽略)
// useState('banana');  
useState([{ text: 'Learn Hooks' }]); //读取到的却是状态变量fruit的值,导致报错

这样一来不能确保hooks 的执行顺序一致。

useMemo

我们都知道 react 一个组件的更新,然后下面的字组件都会更新,有一次被问到class 组件中,如果让不需要更新的组件不更新,当时只想起来了 shouldComponentUpdate,他是在重新渲染的过程中触发的,
PureComponent 就是自动为我们加了shouldComponentUpdate 的方法,如果组件的 props 和 state 都没发生改变, render 方法就不会触发

1、 上面提到了 PureComponent来优化 class 组件,
2、 React.memo() (16.6正式发布的)用户函数组件和PureComponent 很相似

hooks引入了useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

记住,传入 useMemo 的函数会在渲染期间执行 不要再这个函数执行的内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴。

不适用useMemo 之前:


import React , {useState,useMemo} from 'react';
 
function Home(){
     const [name, setName] = useState('名称');
    const [content,setContent] = useState('内容')

    return (
        <>
           <button onClick={() => setName(new Date().getTime())}>name</button>
           <button onClick={() => setContent(new Date().getTime())}>content</button>
            <ChildComponent name={name}>{content}</ChildComponent>
        </>
    )
}

function ChildComponent({name,children}){
    function changeName(name){
        console.log('触发了changeName')
        return name+',改变了name'
    }
 
    const _changeName = changeName(name)
    return (
        <>
            <div>{_changeName}</div>
            <div>{children}</div>
        </>
    )
}

// 点击修改内容,changeName也会触发,每次都会执行。如果我们想要name 变化的时候 changeName 才触发。

使用useMemo 优化:

// 父组件不变

function ChildComponent({name,children}){
    function changeName(name){
        console.log('触发了changeName')
        return name+',改变了name'
    }
 
   // const _changeName = changeName(name)
   const _changeName = useMemo(()=>changeName(name),[name])
    return (
        <>
            <div>{_changeName}</div>
            <div>{children}</div>
        </>
    )
}
// name 变化的时候,changeName 才会触发

useMemo仅在其依赖项数组中的元素发生更改时重新计算值(如果没有依赖项 - 即数组为空,则会在每次调用/呈现时重新计算)。调用该函数不会导致重新渲染。它也在组件的渲染过程中运行,而不是之前

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

推荐阅读更多精彩内容