tstat

react-navigation筆記

安裝依賴

npm install react-native-gesture-handler@1.7.0 react-native-reanimated@1.12.0 react-native-screens@2.10.1 react-native-safe-area-context@3.1.7 @react-native-community/masked-view@0.1.10 @react-navigation/compat@5.2.5 
@react-navigation/native@5.7.3 @react-navigation/stack@5.9.0

在根文件頂部引入,否則應用會崩潰

import 'react-native-gesture-handler';

將整個應用包裝在 NavigationContainer

import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
  );
}

安裝 @react-navigation/stack

npm install @react-navigation/stack

createStackNavigator 是一個返回一個對象(包含2個屬性 ScreenNavigator )的函數。 Navigator 應該包含子元素 Screen ,來定義路由配置。用 NavigationContainer 包裹 Navigator

// In App.js in a new project

import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}

function DetailsScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
    </View>
  );
}

const Stack = createStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ gestureEnabled: true }}>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Overview' }}/>
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

配置

Screen 的必需配置屬性是 namecomponent 。在 Navigator 上配置 initialRouteName

component 屬性接受組件,不接受一個render函數。不要傳一個內聯函數例如 component={() => <HomeScreen />} ,否則當父組件re-render時,你的組件將卸載和重載失去的所有state。

每個路由的配置可以放在 options 里面。相同的可以配在 NavigatorscreenOptions 里面。

傳遞額外的props:

  • (推薦)使用React Context,用context provider包裹navigator去傳數據到screens。
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

  • 用render callback而不是定義一個component prop。默認情況下react-navigation底層做了優化防止重復渲染,但是使用render callback則會移除這些優化。如果用render callback,需要確保Screen組件使用了 React.memoReact.PureComponent
<Stack.Screen name="Home">
  {props => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>

路由跳轉

import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

// ... other code from the previous section
  • navigation 屬性會傳遞給每個在stack navigator中定義的screen component。

  • navigation.navigate('Detail') 調用navigate方法傳入路由name,可以跳轉到對應頁面。

  • navigation.push('Detail') 可以做到在Detail頁面打開另一個Detail頁面。

  • navigation.goBack() 路由回退。

  • 回退多個頁面的方式:

    1. navigate('Home') 回退到Home頁。

    2. navigation.popToTop() 會對到首頁。

function DetailsScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Button
        title="Go to Details... again" onPress={() => navigation.push('Details')} />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
      <Button title="Go back to first screen in stack" onPress={() => navigation.popToTop()}
      />
    </View>
  );
}

路由傳參

步驟:

  1. navigation.navigate('RouteName', { /* params go here */ }) ,推薦使用JSON格式的參數。
  2. 在screen組件中讀取傳入的參數 route.params
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => {
          /* 1. Navigate to the Details route with params */
          navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });
        }}
      />
    </View>
  );
}

function DetailsScreen({ route, navigation }) {
  /* 2. Get the param */
  const { itemId } = route.params;
  const { otherParam } = route.params;
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Text>itemId: {JSON.stringify(itemId)}</Text>
      <Text>otherParam: {JSON.stringify(otherParam)}</Text>
      <Button
        title="Go to Details... again"
        onPress={() => navigation.push('Details', { itemId: Math.floor(Math.random() * 100) }}
      />
      <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

更新路由參數: navigation.setParams

傳參回到上一個頁面: navigation.navigate('',params)

傳參到嵌套的路由器:

navigation.navigate('Account', {
  screen: 'Settings',
  params: { user: 'jane' },
});

配置標題欄

設置標題:(在 Screen 上設置 options

  1. options={{ title: 'My home' }}
  2. options={({ navigation, route }) => ({ title: route.params.name })}options 函數的參數包括 navigationroute ,我們只需用到 route
  3. 使用 navigation.setOptions({ title:'Updated!' }) 更新 options

修改header樣式:(在 Screen 上設置 options

  1. headerStyle :參考View的樣式寫法
  2. headerTintColor
  3. headerTitleStyle :參考Text的樣式寫法
  4. 注意:iOS上,status bar的文字和圖標是黑色的,詳見適配方案

復用常規options:

  1. Stack.Navigator 上配置屬性 screenOptions

自定義組件替換標題: headerTitle (默認是展示text的Text組件)

其他options:詳見文檔

function LogoTitle() {
  return (
    <Image
      style={{ width: 50, height: 50 }}
      source={require('@expo/snack-static/react-native-logo.png')}
    />
  );
}

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ headerTitle: props => <LogoTitle {...props} /> }}
      />
    </Stack.Navigator>
  );
}

header 按鈕

添加header按鈕

  1. headerRight
function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          // 在options里 this 不指向HomeScreen實例,不能調用setState或其他實例方法
          headerTitle: props => <LogoTitle {...props} />,
          headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        }}
      />
    </Stack.Navigator>
  );
}

header與它的screen組件交互

  1. 定義按鈕時,在screen組件內使用 navigation.setOptions 而不是 options 屬性,這樣可以獲取screen的props、state、context等。
function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={({ navigation, route }) => ({
          headerTitle: props => <LogoTitle {...props} />,
        })}
      />
    </Stack.Navigator>
  );
}

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount(c => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

配置回退按鈕

  1. createStackNavigator 提供平臺定制的默認回退按鈕。
  2. headerBackTitle
  3. headerTruncatedBackTitle文檔
  4. headerBackImage文檔

覆蓋回退按鈕

  1. headerRight
  2. headerLeft
  3. 保留button只覆蓋 onPress 方法,可以從 @react-navigation/stack 導出 HeaderBackButton 組件分配給 headerLeft 選項

嵌套導航器(todo)

在一個導航器的Screen內渲染另一個導航器。

  • Stack.Navigator
    • Home ( Tab. Navigator )
      • Feed ( Screen )
      • Messages ( Screen )
    • Profile ( Screen )
    • Settings ( Screen )
function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Messages" component={Messages} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="Profile" component={Profile} />
        <Stack.Screen name="Settings" component={Settings} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

注意:

  • 每個嵌套導航器保持自己的導航歷史
  • 每個嵌套導航器有自己的 options
  • 每個嵌套導航器有自己的參數
    • 如果需要從父Screen傳參到子Screen,可以用React Context透傳參數給children
  • 導航actions(比如 goBacknavigate )會被當前導航器處理,如果處理不了則會冒泡(被父導航器)處理
  • 導航器的特定方法在嵌套導航器中也可用:
    • 如果需要從父導航器dispatch actions到嵌套的子導航器,可以使用 navigation.dispatch
// 從父導航器dispatch actions到嵌套的子導航器
navigation.dispatch(DrawerActions.toggleDrawer());

{/** Drawer的openDrawer方法可以被Stack調用(navigation.openDrawer) */}
<Drawer>
  <Stack />
  {/** ... */}
</Drawer>

{/** Drawer的openDrawer方法不可被Stack下的其他Screen調用 */}
<Stack>
  <Drawer />
  {/** ... */}
</Stack>

{/** Tab可以調用Stack的方法push和replace(navigation.push、navigation.replace) */}
<Stack>
  <Tab/>
  {/** ... */}
</Stack>

嵌套導航器不接收父導航器的事件,如需接受父導航器的事件,需要使用 navigation.dangerouslyGetParent() 顯式監聽

const unsubscribe = navigation
  .dangerouslyGetParent()
  .addListener('tabPress', (e) => {
    // Do something
  });

父導航器的UI渲染在子導航器的最頂端:

  • Drawer的每一個頁面中嵌套Stack:抽屜出現在堆棧標題的上方
  • Stack中嵌套Drawer:抽屜顯式在標題下方
  • Stack的首頁中嵌套Tab:新頁面會覆蓋掉tab bar
  • Tab的每一個頁面中嵌套Stack:tab bar一直顯示,再按一下tab會回到stack的頂部

在嵌套導航器中跳轉頁面

  • Drawer.Navigator
    • Drawer.ScreenHome
    • Drawer.ScreenRoot
      • Stack.ScreenProfile
      • Stack.ScreenSettings
navigation.navigate('Root');
navigation.navigate('Root', { screen: 'Settings' });

在以前的版本中,所有配置都是靜態的,但是現在通過動態配置,直到包含Screen的導航器呈現之前,react-navigation才知道哪些Screen可用以及在哪里可用。通常,Screen在導航進入之前不會渲染任何東西,因此尚未渲染的導航器配置不可用。這使得必須制定要導航到的層次結構。這也是為什么應該盡可能減少嵌套導航器以使代碼更簡單的原因。

將參數傳遞給嵌套導航器中Screen

// 指定params傳遞參數
navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    user: 'jane'
  },
});

// 深層嵌套
navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    screen: 'Sound',
    params: {
      screen: 'Media',
    },
  },
});

渲染定義在導航器中的初始路由

默認情況下,在嵌套導航器中導航Screen時,指定的screen會被用作初始screen,并且導航器上的initialRoute prop會被忽略。

如果需渲染特定的初始路由頁面,可以設置 initial: false

navigation.navigate('Root', {
  screen: 'Settings',
  initial: false,
});

嵌套多個stack 導航器

當嵌套多個Stack導航器時,react-navigation將自動隱藏子stack導航器的標題,以避免重復的標題。但是根據場景不同,顯示子stack導航器的標題,而不是隱藏父stack導航器的標題,可能更有用。可以設置 headerShown: false 解決該問題。完整示例 極少的情況,你需要同事顯示父stack導航器和子stack導航器的標題,可以在子導航器上設置 headerShown: true

function Home() {
  return (
    <NestedStack.Navigator>
      <NestedStack.Screen name="Profile" component={Profile} />
      <NestedStack.Screen name="Settings" component={Settings} />
    </NestedStack.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <RootStack.Navigator>
        <RootStack.Screen
          name="Home"
          component={Home}
          options={{ headerShown: false }}
        />
        <RootStack.Screen name="EditPost" component={EditPost} />
      </RootStack.Navigator>
    </NavigationContainer>
  );
}

嵌套的最佳做法:建議將嵌套導航器減少到最小。嘗試通過盡可能少的嵌套來實現所需的行為。缺點如下

  • 會導致深度嵌套的視圖層級結構,從而可能導致低端設備出現內存和性能問題
  • 嵌套相同類型的導航器可能會導致混淆的用戶體驗
  • 如果嵌套過多,導航到嵌套頁面,配置深層鏈接等時將很難編寫代碼

導航生命周期

堆棧導航器中包含A和B兩個screen。當導航到A頁面時,A的 componentDidMount 被調用,當跳轉到B時,B的 componentDidMount 被調用,但是A在stack中保持mounted,A的 componentWillUnmount 沒有被調用。

當從B回退到A時,B的 componentWillUnmmount 被調用,但是A的 componentDidMount 沒有被調用,因為A一直是mounted狀態。

生命周期事件文檔

  1. focus
  2. blur
function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // Screen was focused
      // Do something
    });

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}
  1. 代替手動添加事件監聽,可以用 useFocusEffect hook去執行副作用,類似react的 useEffect hook。
  2. 使用 useIsFocused hook:返回boolean,表示屏幕是否聚焦
import { useFocusEffect } from '@react-navigation/native';

function Profile() {
  useFocusEffect(
    React.useCallback(() => {
      // Do something when the screen is focused

      return () => {
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, [])
  );

  return <ProfileContent />;
}

打開全屏modal(需要嵌套導航器知識)

在Navigator上設置屬性 mode="modal" ,調用 navigation.navigate 打開modal

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is the home screen!</Text>
      <Button
        onPress={() => navigation.navigate('MyModal')}
        title="Open Modal"
      />
    </View>
  );
}

function DetailsScreen() {
  return (
    <View>
      <Text>Details</Text>
    </View>
  );
}

function ModalScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is a modal!</Text>
      <Button onPress={() => navigation.goBack()} title="Dismiss" />
    </View>
  );
}

const MainStack = createStackNavigator();
const RootStack = createStackNavigator();

function MainStackScreen() {
  return (
    <MainStack.Navigator>
      <MainStack.Screen name="Home" component={HomeScreen} />
      <MainStack.Screen name="Details" component={DetailsScreen} />
    </MainStack.Navigator>
  );
}

function RootStackScreen() {
  return (
    <RootStack.Navigator mode="modal">
      <RootStack.Screen
        name="Main"
        component={MainStackScreen}
        options={{ headerShown: false }}
      />
      <RootStack.Screen name="MyModal" component={ModalScreen} />
    </RootStack.Navigator>
  );
}

注意:

  • mode 可以設置為 card (默認)和 modal (iOS:從底部劃出,從頂部向下滑動以關閉;安卓:無效)
  • 當調用 navigate 時,除了路由外,無需指定其他任何內容。(react-navigation嘗試在最近的導航器上查找路由,然后在該位置執行操作)

Tab Navigator(todo)

文檔

術語表

header: 屏幕頂端的矩形,包含回退按鈕和標題

Navigator :包含子元素 Screen 。NavigationContainer是一個管理導航樹和包含導航狀態的組件。這個組件必須包著所有的導航結構。通常我們在app的頂部(通常在App.js里)渲染這個組件。

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator> // <---- This is a Navigator
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Screen 組件:定義路由配置的組件

被配置在路由配置里的Screen組件(被 navigation.navigate 喚起的Screen)被提供了 navigation props,

const Stack = createStackNavigator();

const StackNavigator = (
  <Stack.Navigator>
    <Stack.Screen
      name="Home"
      component={HomeScreen} // <----
    />
    <Stack.Screen
      name="Details"
      component={DetailsScreen} // <----
    />
  </Stack.Navigator>
);

Navigation prop navigation prop文檔

  • dispatch
  • navigate
  • goBack
  • ...

Route proproute prop文檔

  • params
  • key
  • name

Navigation state:結構如下,index指向當前的路由(B)

{
  key: 'StackRouterRoot',
  index: 1,
  routes: [
    { key: 'A', name: 'Home' },
    { key: 'B', name: 'Profile' },
  ]
}

Route

{
  key: 'B',
  name: 'Profile',
  params: { id: '123' }
}

兼容性層:降低升級的代碼改動成本

  • 安裝 @react-navigation/compat :
npm install @react-navigation/native @react-navigation/compat @react-navigation/stack
  • 使用 createCompatNavigatorFactory 包裹
-import { createStackNavigator } from 'react-navigation-stack';
+import { createStackNavigator } from '@react-navigation/stack';
+import { createCompatNavigatorFactory } from '@react-navigation/compat';
-const RootStack = createStackNavigator(
+const RootStack = createCompatNavigatorFactory(createStackNavigator)(
  {
    Home: { screen: HomeScreen },
    Profile: { screen: ProfileScreen },
  },
  {
    initialRouteName: 'Profile',
  }
);
  • NavigationActions 改為從 @react-navigation/compat 中導出
-import { NavigationActions } from 'react-navigation';
+import { NavigationActions } from '@react-navigation/compat';
  • @react-navigation/compat 導出的api包括:

    • Actions:
      • NavigationActions
      • StackActions
      • DrawerActions
      • SwitchActions
    • HOCs
      • withNavigation
      • withNavigationFocus
    • Navigators
      • createSwitchNavigator
    • Compatibility helpers
      • createCompatNavigatorFactory - 傳入一個使用v5的api的導航器,返回一個使用v4 api的 createXNavigator
      • createCompatNavigationProp - 傳入一個v5 的 navigation 對象 和route對象,返回一個v4的 navigation 對象
  • 兼容層處理了:

    • 使用v4的靜態配置api,俄日不是基于api的組件
    • 改變了navigation對象上的方法名稱去適配v4
    • 添加了對screenProps的支持(v5上被移除)
    • 導出了跟v4同名的action creators,例如NavigationActionsStackActionsSwitchActions
  • 兼容層沒處理:v5動態api導致v4靜態api不再具有某些功能

    • 不再包含navigator的props或者options。意味著你要傳遞給navigator的options可能因為重大改變而不同。
    • 通過在路由配置中定義path來定義舊式深層鏈接是不支持的
    • 導航到導航器的工作原理不同,我們無法導航到尚未渲染的導航器中的屏幕,并且參數不會合并到所有子屏幕。
    • 某些采取一系列操作的方法例如reset將不再支持。
    • 不導出createAppContainer,需要對容器使用v5的apiNavigationContainer
    • 如果使用的高階api例如Redux集成、自定義路由器、actions,這些不再支持,并且你需要移除Redux集成。
  • 為什么用:

    • 允許使用新api寫代碼,同時使用舊api與代碼集成
    • 建立在支持TypeScript的v5之上,舊代碼也可以利用類型檢查功能,對重構有幫助
    • 可以在舊組件中訪問新api,例如navigation.setOptions或者一些新的hook例如useFocusEffect

在沒用navigation props時navigate

  • 有時你需要從一個沒有navigation prop的地方觸發navigation action,比如redux middleware,在這種時候,你可以從navigation容器中dispatch navigation actions。

  • 如果你想要找一種方式,從一個不需要傳navigation prop下去的組件里面navigate,可以用useNavigation。當你可以拿到navigation prop或者useNavigation時不要用這個方法,因為它的行為會有所不同,而且許多特定于屏幕的helper方法將不可用。

  • 你可以通過ref訪問根導航器對象,將其傳遞給RootNavigation,我們稍后將使用該RootNavigation進行導航。

// App.js
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './RootNavigation';

export default function App() {
  return (
    <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
  );
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380