React Native まとめ
スクラップ形式という便利なものがあることを知ったので。
まとめ記事は下記の通り。
結論として、処理速度の観点から値の算出はすべてselectorを利用した方がよく、Recoil依存性の観点からカスタムフックでselectorをラップした方がいいと考える。
ソースは特にない。多分こうでしょレベルだから、何か違った意見があれば随時考え直すべき。
React Native Firebaseのauth().onStateChenged()とRecoilのAtom Effectsの噛み合わせが悪い。
Atom EffectsでonStateChengedをサブスクライブするとログアウト時にユーザ情報が残ったまま更新されてしまうため、ユーザ情報が消えない。
普通にuseEffectでやる分には問題ないんだけど、Atom Effectsを噛ませると普通にuseEffectでやってる方まで変になる。
Atom Effectsは (というかRecoilもだけど) まだまだExperimentalらしいから、その辺がうまく解消されていないのかな。あるいは他にうまいやり方があるのか。
結論として、Atom EffectではなくRecoilないしRecoilStateとの相性が悪いみたい。
const login = () => {
const doLogin = async () => {
try {
await GoogleSignin.hasPlayServices();
const { idToken } = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(idToken);
await auth().signInWithCredential(credential);
} catch (error: unknown) {
// エラー処理
}
};
doLogin();
};
const logout = () => {
const doLogout = async () => {
await auth().signOut();
await GoogleSignin.signOut();
};
doLogout();
};
↑のような関数を定義する。
const [user, setUser] = useState<FirebaseAuthTypes.User | undefined>(undefined);
useEffect(() => auth().onAuthStateChanged(newUser => setUser(newUser ? newUser : undefined)), []);
↑は問題ない。↓はログアウト時に正常に動作しない。
const userState = atom<FirebaseAuthTypes.User | undefined>({
key: 'user',
default: undefined,
});
const [user, setUser] = useRecoilState(userState);
useEffect(() => auth().onAuthStateChanged(newUser => setUser(newUser ? newUser : undefined)), [setUser]);
どういうことなの……
こっちでも指摘されてるけど、解答らしい解答が出てないあたりあんまり直面してる人がいないのかな。
私もcontextで書き直すか……そもそもログアウト機能は実装するつもりないけど、ログアウトを検知できないのは後々問題になっても困るし。
Android版について、デフォルトのパッケージ名だとすでに利用されていることが多い。
そのため、react-native-rename
でパッケージ名を変更するとよい。
$ npx react-native-rename "New App Name" -b com.yourpackage.newapp
アプリ開発を進めてから利用しようとするとエラーを吐くことがあるので、可能であればプロジェクトを作成した直後に走らせるとよい。
最近はパッケージ名を指定できるらしい。
まあ不便だったしねぇ……
$ npx react-native init yourscrap --title "My App" --package-name com.myproject.myapp
最近はhuskyとlint-stagedの記事も見かけないけど、みんな手動でやっているのか基本すぎて誰も何も言わないだけなのか。
$ npm install -D husky lint-staged
{
...,
"scripts": {
...,
"prepare": "husky install"
},
...,
"lint-staged": {
"*.{js,jsx,ts,tsx}": "npm run lint"
}
}
$ npm run prepare
$ npx husky add .husky/pre-commit "npx lint-staged"
基本的に、理由がなければ要素を内包する全てのコンポーネントに style={{ flex: 1 }}
と設定した方がいい。
特に各ページの親コンポーネントはこれだけ設定することも多いから再利用できるように命名しているけど、これっぽっちのためにわざわざ独自コンポーネントを定義するのはどうなのか。
かえって面倒になってない? という疑問。
React NavigationをTypeScript環境で利用する場合、内包するコンポーネントはReact Navigation独自の型に当てはめなきゃいけないんだよね。
これが面倒。仮にReact Navigationを外す場合も上記の型定義を修正しないといけない。
いっそのことクッション的な中間型を作成してコンポジションしてしまおうか。
type RootStackParamList = {
Hoge: undefined;
Fuga: undefined;
}
const Hoge = ({ openFuga }: { openFuga: () => void }) => (
<Button onPress={openFuga}>press me!</Button>
)
const HogeStackScreen = ({ navigation }: NativeStackScreenProps<RootStackParamList, 'Hoge'>) => {
return <Hoge openFuga={() => navigation.navigate('Fuga')} />
}
これでよし。
基本的に、理由がなければ要素を内包する全てのコンポーネントに
style={{ flex: 1 }}
と設定した方がいい。
特に各ページの親コンポーネントはこれだけ設定することも多いから再利用できるように命名しているけど、これっぽっちのためにわざわざ独自コンポーネントを定義するのはどうなのか。
かえって面倒になってない? という疑問。
面倒でした。素直に全部のコンポーネントに style={{ flex:1 }}
を定義していけ。
「スタイルは別ファイルに定義するべき。そうすればコンポーネントのコードは若干きれいになり、必要に応じてIOSとAndroidでのスタイルを別個に定義できる」といった内容。
逆に考えると、たいして汚くならないような小さいコンポーネントやIOSとAndroidの差分を意識する必要がないコンポーネントではいちいちファイルを分ける必要はなさそう?
コンポーネントごとにその辺の書き方を変えるとメンテナンス性が落ちそうだから、なにかしらの基準を設けたいところ。
基本は全部まとめて定義しちゃっていい気もする。
export const withView = <P,>(Component: React.FC<P>, props: P) => {
return (
<View style={{ flex: 1 }}>
<Component {...props} />
</View>
}
HOCだとこういうやり方もできるっぽい。
個人的にはわりと好きだけどどうなんだろうな。今はhooksでだいたい事足りるからHOCは下火らしいし。
いやこれHOCである必要ないな……
まさに探してた記事とGitHubリポジトリ。
こっちだとsrc
のエイリアスに@
を利用してるけど、React Nativeではbabel-plugin-module-resolver
を利用しないといけない都合から@
はうまく動かないっぽい。
久しぶりに開発再開したらWatchmanが動かなくなってたので。
権限エラーの場合、フルディスクアクセス権限を与えて再起動すればOK。
Parameters
やReturnType
みたいなユーティリティ型は使い倒した方がいい。
上位の変更が伝播するため管理箇所が減るし修正が簡単かつ堅牢になる。
useState
を親に持たせて子コンポーネントで参照したい場合、Recoilを利用するか下記のように書く方が簡潔になる。
// ReturnTypeでuseStateを参照した際のundefined混入対策
type UseStateReturnType<T> = ReturnType<typeof useState<T>> extends [
infer S | undefined,
Dispatch<SetStateAction<infer S | undefined>>,
]
? [S, Dispatch<SetStateAction<S>>]
: never;
const Parent = () => {
const countState = useState(0);
return (
<View>
<Child state={countState} />
</View>
);
};
const Child = ({ state: [count, setCount] }: { state: UseStateReturnType<number> }) => {
return (
<View>
<Text>{count}</Text>
<Button title="+1" onPress={() => setCount(count + 1)} />
</View>
);
};
ts-patternでオブジェクトや配列の要素について型チェックを行うとき、異なるブランチの引数として与えられる型は保証されずチェック前の型のままとして与えられる。
type Hoge = {
fuga: string | number;
}
const hoge: Hoge = {
fuga: 'fuga',
}
match(hoge)
.with({ fuga: P.string }, ({ fuga }) => console.log(`ここで${fuga}は string 扱い`))
.otherwise(({ fuga }) => console.log(`ここで${fuga}は string | number 扱い`));
switch文のノリで扱うと上記処理で違和感を覚えることになる。
この問題については下記に記載がある。
要するに、パフォーマンスの観点から上記には特に修正を行う予定はなく、指定した型で扱いたいなら with
で処理を行えばよいとのこと。
otherwise
で処理を行う場合は型指定の必要がない場合のみとし、基本は with
と exhaustive
で処理を行うようにする方が良さそう。
複数の条件で同じ処理を行いたい場合はちょっと面倒になるんだよね……
素直に毎回同じ処理を書くか別の関数に切り出して使うしかなさそう。