Vue随笔

前言

记录一些vue3.x的内容

DOM更新时机

DOM的更新不是同步的。Vue会在“next tick”更新周期中缓冲所有状态的更改,以确保你不管进行了多少次状态更改,每个组件都只会被更新一次。
要等待DOM更新完成后再执行额外的代码,可以使用nextTick()全局API:

import { nextTick } from 'vue'

async function increment() {
  count.value++
  await nextTick()
  // 现在 DOM 已经更新了
}

watch

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

  • 基本示例
<script setup>
import { ref, watch } from 'vue'

const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')

// 可以直接侦听一个 ref
watch(question, async (newQuestion, oldQuestion) => {
  if (newQuestion.indexOf('?') > -1) {
    answer.value = 'Thinking...'
    try {
      const res = await fetch('https://yesno.wtf/api')
      answer.value = (await res.json()).answer
    } catch (error) {
      answer.value = 'Error! Could not reach the API. ' + error
    }
  }
})
</script>

<template>
  <p>
    Ask a yes/no question:
    <input v-model="question" />
  </p>
  <p>{{ answer }}</p>
</template>
  • 监听数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

注意:不能直接监听响应式对象的属性值,例如:

const obj = reactive({ count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

而是应该按如下操作,提供一个getter函数

// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

直接给 watch() 传入一个响应式对象,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发:

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
})

obj.count++

相比之下,一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调:

watch(
  () => state.someObject,
  () => {
    // 仅当 state.someObject 被替换时触发
  }
)

也可以显示加上deep选项,强制转成深层侦听器:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

谨慎使用!深层侦听需要遍历被监听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此,应当只在必要时使用,并且要留意性能。

  • 及时回调侦听器
    watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但如果我们需要立即请求数据,比如请求一些原始数据,然后在相关状态更改时重新请求数据。就需要immediate: true
watch(source, (newValue, oldValue) => {
  // 立即执行,且当 `source` 改变时再次执行
}, { immediate: true })
  • watchEffect()
    观察下面两部分代码,很明显watchEffect的优势就是不用明确指定源,直接在回调中使用需要被监听的数据
const todoId = ref(1)
const data = ref(null)

watch(todoId, async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
}, { immediate: true })
watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

这个例子中,回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect(),我们不再需要明确传递 todoId 作为源值。
注意:watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。

watch 和 watchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
1、 watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
2、 watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。

  • 回调的触发时机
    侦听器能访问到的状态都是Vue更新之前额状态。如果想在侦听器回调中能访问Vue更新之后的DOM,需要flush: 'post'
watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect():

import { watchPostEffect } from 'vue'

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})
  • 停止侦听器
    在setup() 或 <script setup>中创建的都是同步侦听器,与宿主组件绑定了生命周期,不用管。
    但异步创建的侦听器需要手动停止,防止内存泄漏。
<script setup>
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {})

// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

注意,需要异步创建侦听器的情况很少,请尽可能选择同步创建。如果需要等待一些异步数据,你可以使用条件式的侦听逻辑

// 需要异步请求得到的数据
const data = ref(null)

watchEffect(() => {
  if (data.value) {
    // 数据加载后执行某些操作...
  }
})

模板引用

ref 是一个特殊的 attribute,和 v-for 章节中提到的 key 类似。它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

  • 基本使用


注意,你只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在呢!

如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为 null 的情况:

watchEffect(() => {
  if (input.value) {
    input.value.focus()
  } else {
    // 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
  }
})
  • v-for中的使用
<script setup>
import { ref, onMounted } from 'vue'

const list = ref([1, 2, 3])

const itemRefs = ref([])

onMounted(() => {
  alert(itemRefs.value.map(i => i.textContent))
})
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>
  • 函数模块引用
    除了使用字符串值作名字,ref attribute 还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数:
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

注意我们这里需要使用动态的:ref 绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null。你当然也可以绑定一个组件方法而不是内联函数。

  • 组件上的ref
    模板引用也可以被用在一个子组件上。这种情况下引用中获得的值是组件实例:
    这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。

有一个例外的情况,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
  a,
  b
})
</script>

当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 { a: number, b: number } (ref 都会自动解包,和一般的实例一样)。

组件基础

  • 组件的使用
  • 两种props的使用方式
  • 与v-for的联动使用
    在实际应用中,我们可能在父组件中会有如下的一个博客文章数组:
const posts = ref([
  { id: 1, title: 'My journey with Vue' },
  { id: 2, title: 'Blogging with Vue' },
  { id: 3, title: 'Why Vue is so fun' }
])

这种情况下,我们可以使用 v-for来渲染它们:

<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

留意,这里是使用v-bind来传递动态prop值的。当事先不知道要渲染的确切内容时,这一点特别有用。

  • defineEmits
    简单来说就是在setup中使用类似$emit的方式抛出事件,因为非模块中无法访问$emit

和 defineProps 类似,defineEmits 仅可用于 <script setup> 之中,并且不需要导入,它返回一个等同于 emit 方法的 emit 函数。它可以被用于在组件的 <script setup> 中抛出事件,因为此处无法直接访问emit:

<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}
  • 动态组件
<script setup>
import Home from './Home.vue'
import Posts from './Posts.vue'
import Archive from './Archive.vue'
import { ref } from 'vue'
 
const currentTab = ref('Home')

const tabs = {
  Home,
  Posts,
  Archive
}
</script>

<template>
  <div class="demo">
    <button
       v-for="(_, tab) in tabs"
       :key="tab"
       :class="['tab-button', { active: currentTab === tab }]"
       @click="currentTab = tab"
     >
      {{ tab }}
    </button>
      <component :is="tabs[currentTab]" class="tab"></component>
  </div>
</template>
上述效果图

DOM模板解析注意事项

  • 元素位置限制
    某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul>,<ol>,<table> 和 <select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li>,<tr> 和 <option>。
    这将导致在使用带有此类限制元素的组件时出现问题。例如:
<table>
  <blog-post-row></blog-post-row>
</table>

自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute作为一种解决方案:

<table>
  <tr is="vue:blog-post-row"></tr>
</table>

注意:当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。

Props

  • 静态与动态
<BlogPost title="My journey with Vue" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />

<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
  • 传递不同的值类型
    1、 Number
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :likes="42" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :likes="post.likes" />

2、 Boolean

<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />

<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :is-published="false" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />

3、Array

<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :comment-ids="post.commentIds" />

4、 Object

<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />
  • 使用一个对象绑定多个prop
    如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:
  • 单向数据流
    props因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
    1、 prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])

// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)

2、 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:

const props = defineProps(['size'])

// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())

Prop校验

要声明props的校验,可以向defineProps()宏提供一个带有props校验选项的对象,例如:

defineProps({
  // 基础类型检查
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  // 多种可能的类型
  propB: [String, Number],
  // 必传,且为 String 类型
  propC: {
    type: String,
    required: true
  },
  // Number 类型的默认值
  propD: {
    type: Number,
    default: 100
  },
  // 对象类型的默认值
  propE: {
    type: Object,
    // 对象或数组的默认值
    // 必须从一个工厂函数返回。
    // 该函数接收组件所接收到的原始 prop 作为参数。
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个
    // 工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

一些细节:
1、 所有prop默认都是可选的,除非说明了required:true。
2、 除Boolean外的未传递的可选prop将会有一个默认值undefined。
3、 Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。
4、 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。
如果使用了基于类型的 prop 声明,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }}

校验中的type也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来自动检查类型是否匹配。

路由

路由基础部分这部分提过:Vue简记
由于路由导航中的组件实例是被重复使用的,并没有销毁再创建的过程。这也意味着生命周期钩子不会被调用。那么要对同一组件中参数的变化做出响应的话,可以按如下的简单操作:
1、 watch

const User = {
  template: '...',
  created() {
    this.$watch(
      () => this.$route.params,
      (toParams, previousParams) => {
        // 对路由变化做出响应...
      }
    )
  },
}

2、 beforeRouteUpdate 导航守卫

const User = {
  template: '...',
  async beforeRouteUpdate(to, from) {
    // 对路由变化做出响应...
    this.userData = await fetchUser(to.params.id)
  },
}
  • Vue Router和组合式API
    1、在setup中访问路由和当前路由
    因为我们在setup里面没有访问this,所以我们不能再直接访问this.$routerthis.$route。作为替代,我们使用 useRouter 和 useRoute 函数:

2、 name的作用

name属性的意义就在于,我们可以使用这个name属性来动态地绑定当前路由,而不是只能跳转固定的path路径,也就是说,我一旦给某个路由设置了name属性,那么我在跳转的时候,如果是通过绑定的name属性进行跳转,而不是根据绑定的path路径来跳转的话,我的跳转就可以是动态的,即使这个路由path更改了,我的name也是指向当前更改后的路由

注意:如果你想导航到命名路由而不是其嵌套路由,可以命名父路由。但是,当重新加载页面的时候将始终显示嵌套的子路由,因为它被视为指向路径的导航,而不是命名路由。

编程式导航!

顾名思义,就是不借助<router-link>,而是使用逻辑代码的方式实现跳转。

导航到不同位置

其实属性to 和push方法的规则完全相同

  • router.push
    该方法可以导航到其他URL,并且会向history栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的URL。

该方法的参数可以是一个字符串路径,或者一个描述地址的对象:

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

注意:如果提供了path,params会被忽略(params和path不能一起使用),上述例子中的query并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的name或手写完整的带有参数path

const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user

替换当前位置

类似push,但router.replace不会向history添加新记录,正如其名字一样——取代当前的条目。

image.png

也可以直接在传递给router.pushrouteLocation中增加一个属性replace:true

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

横跨历史

该方法采用一个整数作为参数,表示在历史堆中前进或后退多少步,类似于window.history.go(n)

// 向前移动一条记录,与 router.forward() 相同
router.go(1)

// 返回一条记录,与 router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)

篡改历史

你可能已经注意到,router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go的翻版,它们确实模仿了 window.history 的 API。

params和query的用法

1、 params

  • 路由配置
// 使⽤params传参,路由配置的时候 path 要带上参数
{
   path: '/user/:id',
   name: "users",
   component: User    //这个 User 是组件名称
 }
  • 跳转方式
// ⽅法1:
<router-link :to="{ name: 'users', params: { id: this.id}}">按钮</router-link>
// ⽅法2:
this.$router.push({name:'users',params:{id: this.id}})
// ⽅法3:
this.$router.push('/user/' + this.id)
  • 页面url显示
    params在浏览器地址栏中不显示参数名
  • 获取参数方式
this.$route.params.id

2、 query

  • 路由配置
//使⽤ query 传参这⾥不需要参⼊参数,参见上⾯的params写法
{
   path: '/user',    
   name: "users",
   component: User    //这个 users 是传进来的组件名称
 }
  • 跳转方式
// ⽅法1:
<router-link :to="{ name: 'users', query: { id: this.id }}">按钮</router-link>
// ⽅法2:
this.$router.push({ name: 'users', query:{ id: this.id }})
// ⽅法3:
<router-link :to="{ path: '/user', query: { id: this.id }}">按钮</router-link>
// ⽅法4:
this.$router.push({ path: '/user', query:{ id: this.id }})
// ⽅法5:
this.$router.push('/user?id=' +  this.id)
  • 页面url显示
    query在浏览器地址栏中显示参数名称
  • 获取参数
this.$route.query.id

router.push和所有其他导航方法都会返回一个Promise,这让我们可以等到导航完成后才知道是成功还是失败。

命名视图

想同级展示多个视图,而不是嵌套展示(比如slidebar和main两个视图),这个时候命名视图就排上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果router-view没有设置名字,那么默认为default

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用components

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})
  • 嵌套命名视图
    有时候也需要嵌套视图命名。

重定向和别名

/home重定向到/

const routes = [{ path: '/home', redirect: '/' }]

重定向的目标也可以是一个命名的路由

const routes = [{ path: '/home', redirect: { name: 'homepage' } }]

甚至是一个方法,动态返回重定向目标:

const routes = [
  {
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
      return { path: '/search', query: { q: to.params.searchText } }
    },
  },
  {
    path: '/search',
    // ...
  },
]
  • 相对重定向
    重定向到相对位置:
const routes = [
  {
    // 将总是把/users/123/posts重定向到/users/123/profile。
    path: '/users/:id/posts',
    redirect: to => {
      // 该函数接收目标路由作为参数
      // 相对位置不以`/`开头
      // 或 { path: 'profile'}
      return 'profile'
    },
  },
]
  • 别名
    /别名为/home,意味着当用户访问/home时,URL仍然是/home,但会被匹配为用户正在访问/
const routes = [{ path: '/', component: Homepage, alias: '/home' }]

路由组件传参

在组件中使用$route会与路由紧密耦合,这限制了组件的灵活性。可以通过配置props配置来解除这种行为,即当props=true时,route.params将被设置为组件的props

const User = {
  // 请确保添加一个与路由参数完全相同的 prop 名
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]
  • 对于有命名视图的路由,你必须为每个命名视图定义props配置:
  • props是一个对象时,它将原样设置为组件props。当props是静态的时候很有用。
  • 可以创建一个返回props的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。


URL /search?q=vue将传递{query: 'vue'} 作为 props 传给 SearchUser组件。
应尽可能保持props函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义props,请使用包装组件,这样vue才可以对状态变化做出反应。

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

推荐阅读更多精彩内容