React Hooks

Hooks 在 16.8 版本中被添加到 React,允许函数组件访问状态和其他 React 特性。因此,通常不再需要类组件。

尽管 Hooks 通常会替换类组件,但没有计划从 React 中删除类。

什么是 Hooks?

Hooks 允许我们“挂钩”到 React 特性,例如状态和生命周期方法。

import React, { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return (
    <>
      <h1>我最喜欢的颜色是 {color}!</h1>
      <button type='button' onClick={() => setColor('blue')}>
        Blue
      </button>
      <button type='button' onClick={() => setColor('red')}>
        Red
      </button>
      <button type='button' onClick={() => setColor('pink')}>
        Pink
      </button>
      <button type='button' onClick={() => setColor('green')}>
        Green
      </button>
    </>
  )
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

使用 React 提供的钩子前,我们需要从 reactimport 钩子。

这里我们使用 useState 钩子来跟踪应用程序状态。状态通常指需要跟踪的应用程序数据或属性。

挂钩规则

钩子有 3 条规则:

  • Hooks 只能在 React 函数组件内部调用。
  • Hooks 只能在组件的顶层调用。
  • Hooks 不能是有条件的

注意:钩子在 React 类组件中不起作用。

下面我们来看看 React 提供的一些 Hooks。

useState Hooks

React useState 钩子允许我们跟踪函数组件中的状态。状态通常指应用程序中需要跟踪的数据或属性。

导入 useState

要使用 useState 钩子,我们首先需要将 import 它到我们的组件中。

import { useState } from 'react'

我们从 react 中解构 useState,因为它是一个命名导出。

初始化 useState

我们通过在函数组件中调用 useState 来初始化状态。

useState 接受初始状态并返回两个值:

  • 当前状态。
  • 更新状态的函数。
import { useState } from 'react'

function FavoriteColor() {
  const [color, setColor] = useState('')
}

第一个值 color 是我们当前的状态。第二个值 setColor 是用于更新状态的函数。这些名称是变量,可以任意命名。

最后,我们将初始状态设置为空字符串:useState('')

读取状态

我们现在可以在组件中的任何位置包含我们的状态。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return <h1>我最喜欢的颜色是 {color}!</h1>
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

更新状态

为了更新我们的状态,我们使用定义好的 setColor 状态更新程序函数。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function FavoriteColor() {
  const [color, setColor] = useState('red')

  return (
    <>
      <h1>我最喜欢的颜色是 {color}!</h1>
      <button type='button' onClick={() => setColor('blue')}>
        Blue
      </button>
    </>
  )
}

ReactDOM.render(<FavoriteColor />, document.getElementById('root'))

注意:我们不应该直接更新状态。例如:不允许使用 color="red"

状态可以持有什么

useState 钩子可以用来跟踪字符串、数字、布尔值、数组、对象以及它们的任意组合!

我们可以创建多个状态 Hook 来跟踪单个值。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [name, setName] = useState('O.O')
  const [age, setAge] = useState(20)
  const [year, setYear] = useState(1998)

  return (
    <>
      <h1>个人信息</h1>
      <p>
        我叫{name},今年{age}岁,生于{year}年。
      </p>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

或者,我们可以只使用一个状态并包含一个对象!

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [user, setUser] = useState({
    name: 'O.O',
    age: 20,
    year: 1998
  })

  return (
    <>
      <h1>个人信息</h1>
      <p>
        我叫{user.name},今年{user.age}岁,生于{user.year}年。
      </p>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

由于我们现在正在跟踪单个对象,因此在渲染组件时需要引用该对象,然后引用该对象的属性。(例如:user.name

更新状态中的对象和数组

当状态更新时,整个状态都会被覆盖。

如果我们只想更新用户的年龄呢?

如果我们只调用 setUser({ age: 18 }),这将从我们的状态中删除 nameyear

我们可以使用 JavaScript 扩展运算符来帮助我们。

import { useState } from 'react'
import ReactDOM from 'react-dom'

function User() {
  const [user, setUser] = useState({
    name: 'O.O',
    age: 20,
    year: 1998
  })

  const updateUser = () => {
    setUser((previousState) => {
      return { ...previousState, age: 18 }
    })
  }

  return (
    <>
      <h1>个人信息</h1>
      <p>
        我叫{user.name},今年{user.age}岁,生于{user.year}年。
      </p>
      <button onClick={updateUser}>18</button>
    </>
  )
}

ReactDOM.render(<User />, document.getElementById('root'))

因为我们需要状态的当前值,所以我们将一个函数传递给 setUser 函数。此函数接收上一个值。

然后我们返回一个对象,展开 previousState 并仅覆盖 age

useEffect Hooks

useEffect Hook 允许您在组件中执行副作用。副作用的一些示例如:获取数据、直接更新 DOM 和定时器。

useEffect 接受两个参数。第二个参数是可选的。

以定时器为例,使用 setTimeout() 计算初始渲染后的 1 秒:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

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

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)
  })

  return <h1>我已经渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

可是等等!!它一直在计数,即使它应该只计数一次!

useEffect 在每个渲染上运行。这意味着当计数发生变化时,会发生渲染,然后触发另一个效果。

这不是我们想要的。有几种方法可以控制副作用何时运行。

我们应该始终包含接受数组的第二个参数。我们可以选择将依赖项传递给该数组中的 useEffect

没有任何依赖:

useEffect(() => {
  // 在每个渲染上运行
})

一个空数组:

useEffect(() => {
  // 仅在第一次渲染时运行
}, [])

propstate 值:

useEffect(() => {
  // 在第一次渲染和任何依赖项值更改时运行
}, [prop, state])

所以,为了解决这个问题,让我们只在初始渲染上运行这个效果。

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

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

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)
  }, []) // <- 在此处添加空括号

  return <h1>我渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

下面是一个依赖于变量的 useEffect 钩子的示例。如果 count 变量更新,效果将再次运行:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

function Counter() {
  const [count, setCount] = useState(0)
  const [calculation, setCalculation] = useState(0)

  useEffect(() => {
    setCalculation(() => count * 2)
  }, [count]) // <- 在这里添加 count 变量

  return (
    <>
      <p>总数: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
      <p>计算: {calculation}</p>
    </>
  )
}

ReactDOM.render(<Counter />, document.getElementById('root'))

如果存在多个依赖项,则应将它们包含在 useEffect 依赖项数组中。

清理 Effect

有些效果需要清理以减少内存泄漏。

超时、订阅、事件监听器和其他不再需要的效果应该被处理。

我们通过在 useEffect 钩子的末尾包含一个返回函数来实现这一点。

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

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

  useEffect(() => {
    let timer = setTimeout(() => {
      setCount((count) => count + 1)
    }, 1000)

    return () => clearTimeout(timer)
  }, [])

  return <h1>我渲染了 {count} 次!</h1>
}

ReactDOM.render(<Timer />, document.getElementById('root'))

注意:要清除定时器,我们必须为其命名。

useContext Hooks

React Context 是一种全局管理状态的方法。

与单独使用 useState 相比,它可以与 useState 钩子一起使用,在深度嵌套的组件之间更容易地共享状态。

问题

状态应由堆栈中需要访问状态的最高父组件持有。

举例来说,我们有许多嵌套组件。堆栈顶部和底部的组件需要访问状态。

要在没有上下文的情况下实现这一点,我们需要将状态作为 props 传递给每个嵌套组件。这被称为 prop drilling

import { useState } from 'react'
import ReactDOM from 'react-dom'

function Comp1() {
  const [user, setUser] = useState('O.O')

  return (
    <>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </>
  )
}

function Comp2({ user }) {
  return (
    <>
      <h1>组件 2</h1>
      <Comp3 user={user} />
    </>
  )
}

function Comp3({ user }) {
  return (
    <>
      <h1>组件 3</h1>
      <Comp4 user={user} />
    </>
  )
}

function Comp4({ user }) {
  return (
    <>
      <h1>组件 4</h1>
      <Comp5 user={user} />
    </>
  )
}

function Comp5({ user }) {
  return (
    <>
      <h1>组件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

ReactDOM.render(<Comp1 />, document.getElementById('root'))

即使组件 2-4 不需要状态,但它们也必须传递状态才能到达组件 5。

解决方案

解决方案是创建上下文。

要创建上下文,必须导入 createContext 并对其进行初始化:

import { useState, createContext } from 'react'
import ReactDOM from 'react-dom'

const UserContext = createContext()

接下来,我们将使用 Context Provider 来包装需要状态上下文的组件树。

Context Provider

在 Context Provider 中包装子组件并提供状态值。

function Comp1() {
  const [user, setUser] = useState('O.O')

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </UserContext.Provider>
  )
}

现在,该树中的所有组件都可以访问用户上下文。

使用 useContext Hooks

为了在子组件中使用上下文,我们需要使用 useContext 钩子访问它。

首先,导入 useContext

import { useState, createContext, useContext } from 'react'

然后,您可以访问所有组件中的用户上下文:

function Comp5() {
  const user = useContext(UserContext)

  return (
    <>
      <h1>组件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

完整示例:

import { useState, createContext, useContext } from 'react'
import ReactDOM from 'react-dom'

const UserContext = createContext()

function Component1() {
  const [user, setUser] = useState('O.O')

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Comp2 user={user} />
    </UserContext.Provider>
  )
}

function Comp2() {
  return (
    <>
      <h1>组件 2</h1>
      <Comp3 />
    </>
  )
}

function Comp3() {
  return (
    <>
      <h1>组件 3</h1>
      <Comp4 />
    </>
  )
}

function Comp4() {
  return (
    <>
      <h1>组件 4</h1>
      <Comp5 />
    </>
  )
}

function Comp5() {
  const user = useContext(UserContext)

  return (
    <>
      <h1>组件 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  )
}

ReactDOM.render(<Component1 />, document.getElementById('root'))

useRef Hooks

useRef 钩子允许在渲染之间持久化值。

它可用于存储在更新时不会导致重新渲染的可变值,也可用于直接访问 DOM 元素。

不会导致重新渲染

如果我们试图计算应用程序使用 useState 钩子渲染的次数,我们将陷入无限循环,因为这个钩子本身会导致重新渲染。

为了避免这种情况,我们可以使用 useRef 钩子。

import { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const [inputValue, setInputValue] = useState('')
  const count = useRef(0)

  useEffect(() => {
    count.current = count.current + 1
  })

  return (
    <>
      <input
        type='text'
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h1>渲染次数: {count.current}</h1>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

useRef() 只返回一项。它返回一个名为 current 的对象。

初始化 useRef 时,我们设置初始值为 useRef(0)。它其实类似于 const count= { current:0 },我们可以使用 count 访问 count.current

访问 DOM 元素

通常,我们希望让 React 处理所有 DOM 操作。

但在某些情况下,可以使用 useRef 而不会引起问题。

在 React 中,我们可以向元素添加 ref 属性,以便直接在 DOM 中访问它。

import { useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const inputElement = useRef()

  const focusInput = () => inputElement.current.focus()

  return (
    <>
      <input type='text' ref={inputElement} />
      <button onClick={focusInput}>聚焦 input</button>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

跟踪状态变化

useRef 钩子还可用于跟踪先前的状态值。

这是因为我们能够在渲染之间持久化 useRef 值。

import { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'

function App() {
  const [inputValue, setInputValue] = useState('')
  const previousInputValue = useRef('')

  useEffect(() => {
    previousInputValue.current = inputValue
  }, [inputValue])

  return (
    <>
      <input
        type='text'
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <h2>当前值: {inputValue}</h2>
      <h2>先前值: {previousInputValue.current}</h2>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

这一次,我们结合使用 useStateuseffectuseRef 来跟踪之前的状态。

useEffect 中,每次通过在 input 字段中输入文本来更新 inputValue 时,我们都会更新 useRef 当前值。

useReducer Hooks

useReducer 钩子类似于 useState 钩子。它允许自定义状态逻辑。

如果您发现自己在跟踪依赖于复杂逻辑的多个状态,useReducer 可能会很有用。

useReducer 钩子接受两个参数:

  • reducer函数包含自定义状态逻辑,initialState 可以是一个简单的值,但通常会包含一个对象。
  • useReducer 钩子返回当前 statedispatch 方法。

下面是计数器使用 useReducer 的示例:

import { useReducer } from 'react'
import ReactDOM from 'react-dom'

const initialTodos = [
  {
    id: 1,
    title: 'Todo 1',
    complete: false
  },
  {
    id: 2,
    title: 'Todo 2',
    complete: false
  }
]

const reducer = (state, action) => {
  switch (action.type) {
    case 'COMPLETE':
      return state.map((todo) => {
        if (todo.id === action.id) {
          return { ...todo, complete: !todo.complete }
        } else {
          return todo
        }
      })
    default:
      return state
  }
}

function Todos() {
  const [todos, dispatch] = useReducer(reducer, initialTodos)

  const handleComplete = (todo) => {
    dispatch({ type: 'COMPLETE', id: todo.id })
  }

  return (
    <>
      {todos.map((todo) => (
        <div key={todo.id}>
          <label>
            <input
              type='checkbox'
              checked={todo.complete}
              onChange={() => handleComplete(todo)}
            />
            {todo.title}
          </label>
        </div>
      ))}
    </>
  )
}

ReactDOM.render(<Todos />, document.getElementById('root'))

这就是跟踪 todo 完成状态的逻辑。

通过添加更多操作,添加、删除和完成 todo 的所有逻辑都可以包含在单个 useReducer 钩子中。

useCallback Hooks

React useCallback Hook 返回一个已记忆的回调函数。

这使我们能够隔离资源密集型函数,以便它们不会在每次渲染时自动运行。

useCallbackHook 仅在其依赖项之一更新时运行,提高了性能。

问题

使用 useCallback 的一个原因是防止组件重新渲染,除非其 props 已更改。

在本例中,您可能会认为 todos 组件不会重新渲染,除非 todos 发生更改:

// main.js
import { useState } from 'react'
import ReactDOM from 'react-dom'
import Todos from './Todos'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        次数: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Todos.js 组件:

import { memo } from 'react'

const Todos = ({ todos, addTodo }) => {
  console.log('子渲染')
  return (
    <>
      <h2>Todos List</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>
      })}
      <button onClick={addTodo}>添加 Todo</button>
    </>
  )
}

export default memo(Todos)

尝试运行它并单击 + 按钮。

您会注意到,即使 todos 没有更改,Todos 组件也会重新渲染。

为什么这不起作用?我们使用的是 memo,所以 Todos 组件不应该重新渲染,因为当 count增加时,todos 状态和 addTodo 函数都没有改变。

这是因为所谓的“参照平等”。

每次组件重新渲染时,都会重新创建其函数。因此,addTodo 函数实际上发生了变化。

解决方案

为了解决这个问题,我们可以使用 useCallback 钩子来防止函数被重新创建,除非有必要。

使用 useCallback 钩子可以防止 Todos 组件不必要地重新渲染:

import { useState, useCallback } from 'react'
import ReactDOM from 'react-dom'
import Todos from './Todos'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = useCallback(() => {
    setTodos((t) => [...t, 'New Todo'])
  }, [todos])

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        次数: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Todos.js 组件:

import { memo } from 'react'

const Todos = ({ todos, addTodo }) => {
  console.log('子渲染')
  return (
    <>
      <h2>Todos List</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>
      })}
      <button onClick={addTodo}>新增 Todo</button>
    </>
  )
}

export default memo(Todos)

useMemo Hooks

React useMemo 钩子返回一个已记忆的值,它仅在其中一个依赖项更新时运行,提高性能。

useMemouseCallback 钩子类似。主要区别在于 useMemo 返回一个已记忆的值, useCallback 返回一个已记忆的函数。

性能

useMemo 钩子可以用来防止昂贵的、资源密集型的函数不必要地运行。

在本例中,我们有一个在每个渲染上运行的昂贵函数。

更改计数或添加 todo 时,您会注意到执行延迟。

import { useState } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])
  const calculation = expensiveCalculation(count)

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <div>
      <div>
        <h2>Todos List</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>
        })}
        <button onClick={addTodo}>新增 Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>昂贵的计算</h2>
        {calculation}
      </div>
    </div>
  )
}

const expensiveCalculation = (num) => {
  console.log('计算...')
  for (let i = 0; i < 1000000000; i++) {
    num += 1
  }
  return num
}

ReactDOM.render(<App />, document.getElementById('root'))

使用 useMemo

为了解决这个性能问题,我们可以使用 useMemoHook 来记忆 expensiveCalculation 函数。这将导致该函数仅在需要时运行。

useMemoHook 接受第二个参数来声明依赖项。昂贵的函数只会在其依赖关系发生变化时运行。

在下面的示例中,昂贵的函数只会在 count 更改时运行,而不是在添加待办事项时运行。

import { useState, useMemo } from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [count, setCount] = useState(0)
  const [todos, setTodos] = useState([])
  const calculation = useMemo(() => expensiveCalculation(count), [count])

  const increment = () => {
    setCount((c) => c + 1)
  }
  const addTodo = () => {
    setTodos((t) => [...t, 'New Todo'])
  }

  return (
    <div>
      <div>
        <h2>Todos List</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>
        })}
        <button onClick={addTodo}>新增 Todo</button>
      </div>
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
        <h2>昂贵的计算</h2>
        {calculation}
      </div>
    </div>
  )
}

const expensiveCalculation = (num) => {
  console.log('计算...')
  for (let i = 0; i < 1000000000; i++) {
    num += 1
  }
  return num
}

ReactDOM.render(<App />, document.getElementById('root'))

自定义 Hooks

Hooks 是可重用的函数。

当您有需要在多个组件中使用相同的组件逻辑时,我们可以将该逻辑提取到自定义 Hook。

自定义 Hooks 以 use 开头。本节将编写一个 useFetch 示例。

自定义 useFetch 钩子

在下面的代码中,我们在 Home 组件中获取数据并显示它。

我们将使用 JSONPlaceholder 服务来获取假数据。

使用 JSONPlaceholder 服务获取假 Todos 列表,并在页面上显示标题:

import { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'

const Home = () => {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/todos')
      .then((res) => res.json())
      .then((data) => setData(data))
  }, [])

  return (
    <>
      {data &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>
        })}
    </>
  )
}

ReactDOM.render(<Home />, document.getElementById('root'))

其他组件也可能需要获取逻辑,因此我们将其提取到自定义 Hook 中。

将获取逻辑移动到一个新文件以用作自定义 Hook:

// useFetch.js
import { useState, useEffect } from 'react'

const useFetch = (url) => {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => setData(data))
  }, [url])

  return [data]
}

export default useFetch
// main.js
import ReactDOM from 'react-dom'
import useFetch from './useFetch'

const Home = () => {
  const [data] = useFetch('https://jsonplaceholder.typicode.com/todos')

  return (
    <>
      {data &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>
        })}
    </>
  )
}

ReactDOM.render(<Home />, document.getElementById('root'))

我们创建了一个名为 useFetch.js 的新文件,其中包含一个名为 useFetch 的函数,该函数包含获取数据所需的所有逻辑。

我们删除了硬编码的 URL,并将其替换为可以传递给自定义钩子的 URL 变量。

最后,我们从钩子中返回数据。

main.js 中,我们导入 useFetch 钩子,并像其他钩子一样使用它。这就是我们传递 URL 以从中获取数据的地方。

现在我们可以在任何组件中重用这个自定义钩子,从任何 URL 获取数据。

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

推荐阅读更多精彩内容