react native 开发

1.style

1.1 使用StyleSheet来定义样式

style 不支持 css 继承,使用StyleSheet来定义样式

import React from "react";
import { StyleSheet, Text, View } from "react-native";

const LotsOfStyles = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.red}>just red</Text>
      <Text style={styles.bigBlue}>just bigBlue</Text>
      <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
      <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    marginTop: 50,
  },
  bigBlue: {
    color: "blue",
    fontWeight: "bold",
    fontSize: 30,
  },
  red: {
    color: "red",
  },
});

export default LotsOfStyles;

1.2 样式单位

不支持vh,vw,px

1.3 border 不支持复合写法

2.flexbox

flexbox 默认为flex-direction:'column

web 端为默认flex-direction:'row

3.响应式布局

3.1 dimensions(尺寸)

响应式布局计算字体大小

默认设计稿为 750px

import { Dimensions } from "react-native";

const { width, height } = Dimensions.get("window");
const mode = height > width ? "portrait" : "landscape";
const clientWidth = mode === "portrait" ? width : height;

export function font(num: number, designWidth?: number): number {
  designWidth = designWidth || 750;
  const fontSize = (clientWidth * 2) / designWidth;
  return num * fontSize;
}

const styles = StyleSheet.create({
  container: {
    height: 100,
  },
  text: {
    fontSize: font(12),
  },
});

4.整体架构

配置@别名babel.config.js

yarn add babel-plugin-module-resolver -D
module.exports = {
  presets: ["module:metro-react-native-babel-preset"],
  plugins: [
    [
      "module-resolver",
      {
        root: ["./src"],
        extensions: [".ios.js", ".android.js", ".js", ".ts", ".tsx", ".json"],
        alias: {
          "@": ["./src"],
        },
      },
    ],
  ],
};

配置 tsConfigts.config.json

{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

增加prettier format脚本

{
  "scripts": {
    "format": "npx prettier --write \"./src/**/*.ts\" \"./src/**/*.tsx\" "
  }
}

5.i18n 国际化

参考文献open in new window

安装i18nextreact-native-localize

yarn add react-i18next i18next
yarn add react-native-localize
# ios
npx pod-install

新建locales/en-us.json

{
  "hello": "hello"
}

新建locales/zh-cn.json

{
  "hello": "你好"
}

新建locales/index.ts

import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import * as RNLocalize from "react-native-localize";
import storage from "./storage";
import { reset } from "./RootNavigation";

export const lngKey = "@lng";

const languageDetector = {
  type: "languageDetector",
  async: true,
  detect: function (callback) {
    // 获取上次选择的语言
    storage.get(lngKey, "locale").then((lng) => {
      // 如果是跟随本地,则获取系统语言
      if (lng === "locale") {
        callback(getSystemLanguage());
      } else {
        callback(lng);
      }
    });
  },
};

// 初始化i18next配置
i18next
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "zh", // 切换语言失败时的使用的语言
    debug: __DEV__, // 开发环境开启调试
    // 资源文件
    resources: {
      en: {
        translation: require("./en-US.json"),
      },
      zh: {
        translation: require("./zh-CN.json"),
      },
    },
    react: {
      useSuspense: false,
    },
  });

/**
 * 获取当前系统语言
 * @returns
 */
export const getSystemLanguage = (): string => {
  const locales = RNLocalize.getLocales();
  return locales[0].languageCode;
};

/**
 * 切换语言
 * @param lng
 */
export const changeLanguage = async (lng?: "en" | "zh" | "locale") => {
  // 切换语言
  await i18next.changeLanguage(lng === "locale" ? getSystemLanguage() : lng);
  // 持久化当前选择
  await storage.set(lngKey, lng);
};

export default i18next;

app.js

import "@/locales";

react-native中使用

import React from "react";
import { Text } from "react-native";
import { useTranslation } from "react-i18next";

export function MyComponent() {
  const { t, i18n } = useTranslation();
  // or const [t, i18n] = useTranslation();

  return <Text>{t("hello")}</Text>;
}

6.生成二维码

参考文献open in new window

使用react-native-qrcode-svg react-native-svg

添加依赖

yarn add react-native-qrcode-svg react-native-svg

简单使用:

import QRCode from "react-native-qrcode-svg";

function Page() {
  return <QRCode value="This is the value in the QRcode" />;
}

7.启动时闪屏react-native-splash-screen

参考资料open in new window

安装:

yarn add react-native-splash-screen

8.路由navigation

基本路由:

import {
  createStackNavigator,
  CardStyleInterpolators,
  CommonActions,
  HeaderStyleInterpolators,
} from "@react-navigation/stack";
import { NavigationContainer } from "@react-navigation/native";

import React from "react";
import Login from "@/pages/user/Login";

<NavigationContainer>
  <Stack.Navigator
    initialRouteName="Login"
    screenOptions={{
      headerTitleAlign: "center",
      headerStyleInterpolator: HeaderStyleInterpolators.forUIKit,
      cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
      gestureEnabled: true,
      gestureDirection: "horizontal",
    }}
  >
    <Stack.Screen
      name="Login"
      options={{
        headerShown: false,
      }}
      component={Login}
    />
    <Stack.Screen
      name="Home"
      options={{
        headerShown: false,
      }}
      component={Home}
    />
  </Stack.Navigator>
</NavigationContainer>;

reset

navigation.reset({
  index: 0,
  routes: [
    {
      name: "Home",
      params: { someParam: "Param1" },
    },
  ],
});

函数式 reset:

import { CommonActions } from "@react-navigation/native";

navigation.dispatch(
  CommonActions.reset({
    index: 1,
    routes: [
      { name: "Home" },
      {
        name: "Profile",
        params: { user: "jane" },
      },
    ],
  })
);

9.服务端事件SSE

服务端:sever.js

const express = require("express");
const http = require("http");
const { Server } = require("http");
const { EventEmitter } = require("events");

const app = express();
const server = http.createServer(app);
const eventEmitter = new EventEmitter();

app.use(express.static("public"));

app.get("/api/events", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  const listener = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  eventEmitter.on("message", listener);

  req.on("close", () => {
    eventEmitter.off("message", listener);
  });
});

// Endpoint to send messages to clients
app.post("/api/send-message", (req, res) => {
  const message = { text: "Hello from server!" };
  console.log("/api/send-message");
  eventEmitter.emit("message", message);
  res.json({ success: true });
});

setInterval(() => {
  const message = "rolling counter:" + Date.now();
  console.log(message);
  eventEmitter.emit("message", message);
}, 3000);

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

web 客户端

<script setup lang="ts">
const eventSource = new EventSource("/api/events");

eventSource.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  console.log("Received message in Web Browser:", data);
});

// Example of sending a message to the server

const click = () => {
  fetch("/api/send-message", {
    method: "POST",
  });
};
</script>

<template>
  <div @click="click">clickclickclick</div>
</template>

<style scoped></style>

react-native示例

import EventSource from "react-native-sse";
import "react-native-url-polyfill/auto"; // Use URL polyfill in React Native

const SSEExample = () => {
  useEffect(() => {
    console.log("SSEExample");

    const es = new EventSource("http://192.168.1.9:3000/api/events");

    es.addEventListener("message", (event) => {
      const data = JSON.parse(event?.data);
      console.log("Received message in React Native:", data);
    });

    // const es = new EventSource('http://192.168.1.9:3000/api/event');

    // es.addEventListener('open', event => {
    //   console.log('Open SSE connection.');
    // });

    // es.addEventListener('message', event => {
    //   const data = JSON.parse(event.data);
    //   console.log('Received message in React Native:', data);
    // });

    // es.addEventListener('error', event => {
    //   if (event.type === 'error') {
    //     console.error('Connection error:', event.message);
    //   } else if (event.type === 'exception') {
    //     console.error('Error:', event.message, event.error);
    //   }
    // });

    // es.addEventListener('close', event => {
    //   console.log('Close SSE connection.');
    // });

    return () => {
      es.removeAllEventListeners();
      es.close();
    };
  }, []);

  function sendMessage() {
    console.log("sendMessage");
    fetch("http://192.168.1.9:3000/api/send-message", { method: "POST" });
  }

  return (
    <View>
      <Text>SSE</Text>
      <TouchableOpacity
        onPress={sendMessage}
        style={{ backgroundColor: "#ccc", width: "100%" }}
      >
        <Text style={{ textAlign: "center", lineHeight: 80 }}>sendMessage</Text>
      </TouchableOpacity>
    </View>
  );
};

关于react-native 的 sse 在 android 被拦截的问题,参见react-native/issues/28835open in new window

Try to disable Flipper network interceptor. Go to android/app/src/debug/java//ReactNativeFlipper.java and comment next lines of code:

  // try to comment this code
  NetworkingModule.setCustomClientBuilder(
    new NetworkingModule.CustomClientBuilder() {
      @Override
      public void apply(OkHttpClient.Builder builder) {
        builder.addNetworkInterceptor(new     FlipperOkhttpInterceptor(networkFlipperPlugin));
      }
    }
  );

10 lottie 动画

success.jsonopen in new window

安装 lottie

yarn add lottie-react-native
import LottieView from "lottie-react-native";

const animationRef = useRef<LottieView>(null);

function play() {
  animationRef?.current?.play?.();
  console.log("onAnimationplay");
}

function onAnimationFinish() {
  console.log("onAnimationFinish");
  animationRef?.current?.pause?.();
}

<LottieView
  ref={animationRef}
  style={{ width: 200, height: 200 }}
  source={require("@/assets/animation/success.json")}
  duration={4000}
  loop={false}
  autoPlay={false}
  onAnimationFinish={onAnimationFinish}
/>;
Contributors: masecho