有名テック企業の技術ブログを、ひとつのフィードで。
フィード
42件
こんにちは、アクセシビリティエンジニアでSmartHR UIコアメンバーの tajiman です。 SmartHR UI は SmartHR のプロダクトで使われているコンポーネントライブラリです。この記事では、SmartHR UI の2025年の活動を振り返ります。 ※2025年度ではなく、2025年の活動です。いいえ、間違いではありません。 SmartHRでは、SmartHR UI がどのように成長してきたか毎年振り返りをしています。 昨年の振り返り記事はこちらです。 SmartHR UI の現在地 SmartHR UI の2024年12月時点と2025年12月時点の数値を比較します。 項目 2024/12/13 2025/12/18 バージョン v62.2.0 v82.0.0 コンポーネント数 *1 100 99 ソースコード規模 *2 32,108行 43,664行 コミット数 *3 5,754 6,430 コントリビューター数 *4 83人 119人 バージョンは v62.2.0 から v82.0.0 へと20のメジャーバージョンが上がりました。コントリビューター数は83人から119人に増えました。 コンポーネント数が100→99と微減しているのは、リネーム(CheckBox→Checkbox 等)に伴う統廃合や ActionDialogContent、MessageScreen 等の削除が、新規追加(SmartHRAILogo、StepFormDialog等14個)を一部相殺しているためです。 コントリビューター 2025年のコミット数上位10名を紹介します(同率10位を含む)。 順位 名前 コミット数 1 Mizoue Atsushi 187 2 たふみ 82 3 KANAMORI Yu 26 4 Tajima Sachiko 13 5 Ryō Igarashi 9 6 shingo.sasaki 5 6 Kei Kono 5 6 oti 5 6 Misako Tateiwa 5 10 nabeliwo 4 10 Takatsugu Kageyama 4 10 Daichi Hashimura 4 コントリビューターの皆さん、ありがとうございます。 開発・運用体制 SmartHR UI のコアメンバーは7名(エンジニア4名、デザイナー3名)で構成されています。昨年と同様に専任のメンバーはおらず、有志による運営を続けています。 週次の定例ミーティングを開催し、課題の共有や方針の議論を行っています。 リリース リリース作業は昨年に引き続き自動化されており、週1回のペースで実施しています。2025年は v63.0.0 から v82.0.0 まで、20のメジャーバージョンをリリースしました。 アクセシビリティへの取り組み SmartHR UI ではアクセシビリティの品質向上に継続的に取り組んでいます。 2025年には12件のアクセシビリティに関するPRがマージされました。 代表的なものとして チャートを色のみで表現しないようにした というPRがあります。 色だけに頼らず、模様(パターン)で系列を区別できる棒グラフ 棒グラフを色のみではなくパターンを付けることで、色覚特性をもつユーザーでも判別ができるようにしました。 ESLint カスタムルール 2025年には、テーブルセル内でのCheckboxやRadioButtonの使用を禁止するルールなど、12件の新しいルールが追加されました。 アクセシビリティ関連:18 ルール その他:34 ルール 合計 52 ルール おわりに SmartHR UI は2025年も多くのコントリビューターに支えられながら成長を続けました。 まだまだやりたいことが山ほどあるため、引き続き沢山のコントリビューターとレビュワーを募集しています! We Are Hiring! SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です! 少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう! hello-world.smarthr.co.jp *1:packages/smarthr-ui/src/index.ts から export されているコンポーネントの数をカウント。Icon は export * でまとめてエクスポートされているが1つとしてカウント。hooks・テーマ・定数・i18n 関連の export はカウント対象外。なお、2024年以前の計測とは数え方が若干異なる(以前は Storybook を作成している外部向けコンポーネントの数をカウントしていた)。 *2:cloc で packages/smarthr-ui/src を計測(空行・コメント除く)。2024年以前と同条件で測ると35,137→43,664と大幅に増えていますが、2024年のブログ値(32,108)とは計測日・条件の差異がある可能性があります。 *3:bot によるライブラリのアップデートを含む。 *4:bot を含む。
こんにちは!SmartHRでプロダクトエンジニアをしているTaiです。 2025年12月にSmartHRに入社し、勤怠管理の開発チームに所属しています。 前職ではフロントエンドエンジニアとしてOCR(光学文字認識)やSCM(サプライチェーン管理)のプロダクト開発に携わっていました。HR・労務ドメインのプロダクト開発は今回が初めてです。 入社から約半年が経ち、チームの中にいることが自然になってきた今だからこそ、ジョイン当初に感じた驚きや発見を残しておきたいと思いこの記事を書いています。 私たちが開発している勤怠プロダクトは、法令・制度・企業ごとの就業規則が複雑に交差するドメインで、入社直後は「これは一筋縄ではいかないな」と率直に感じました。しかし、チームにはその複雑さを乗り越えるためのサポート体制が想像以上に整っていました。会社のバリューがチーム文化に深く浸透していることにも驚かされます。 外から来た目線がまだ残っているうちに、チームの特徴を「文化」と「技術・開発体制」の2つの軸でお伝えしていきます。 チーム文化の特徴 「実装するだけ」の人間がいない 私たちのチームでは、エンジニア全員がユーザー目線を強く意識しています。何かを実装する前に、まず「ユーザーにとってなぜそれが必要なのか」というWhy(目的)を徹底的に掘り下げます。 How(手段)を詰める前にWhy(目的)を大事にする姿勢がチーム全体に根付いています。 そのために、仕様検討などを職種関係なくチームメンバー全員で決めていく形をとっており、エンジニアも要件や仕様の段階から主体的に参加し、「何を作るべきか」をチーム全員で考えます。 この体制は良い循環を生んでいます。仕様検討に関わることでドメイン理解が深まり、その理解が進むことでより良い設計判断ができるようになり、結果としてプロダクトの質が上がっていきます。 私が見てきた限りでは、「言われたものを作る」のではなく、メンバー一人ひとりがプロダクトに対してオーナーシップを持って開発に向き合っているチームです。 リモートでも距離を感じないコミュニケーション チームはフルリモートで開発を行っており、だからこそテキストコミュニケーションを大切にしています。Slackでのやり取りが盛んで、ミーティング中もスレッドで議論や補足が活発に飛び交います。 リモートワークでありがちな「聞きづらい」「様子がわからない」といった距離感は全く感じません。 新しく入ったメンバーへのサポートも手厚く整っています。勤怠プロダクトの開発組織内には複数のチームがありますが、チームを横断して話せる場が設けられています。 自分のチームに閉じず、チーム内外を問わず気軽に相談できるので、ジョイン直後の不安感はかなり和らぎました。 挑戦とフィードバックが日常にある雰囲気 チームには、質問や提案を安心して出せる雰囲気があります。その土台にあるのは、「光の速さでまず動く」というSmartHRが掲げている三つのバリューのうちの一つでしょう。「まずやってみる」を大切にし、挑戦を後押しする姿勢がチーム全体に根付いています。 挑戦が前提なので、間違いを恐れる空気がありません。仮にうまくいかなかったとしても、建設的なフィードバックが返ってきます。 挑戦とフィードバックがセットになっているからこそ、安心して新しいことに踏み出して成長できる環境になっていると思います。 何か気づいたことがあればすぐにチーム全体にフィードバックを求める姿勢があり、それは1on1やふりかえりに限らず、日常のSlackのやり取りの中でも自然に行われています。 フィードバックが特別なイベントではなく、日常の一部になっているチームです。 また、SmartHRにはバリューを表すSlackのスタンプが用意されていて、誰かのアクションに対して都度リアクションとして押されているのをよく目にします。 お互いの行動をバリューの観点から認め合う文化が、こうした小さな習慣を通じて自然と根付いているのだと思います。 認識の「ズレ埋め」文化 SmartHRには「ズレ埋め」という文化があります。認識のズレをそのままにせず、気づいた時点でお互いに指摘し合う文化です。ズレが小さいうちに修正できるので、一人で抱え込んで問題が大きくなるということが起きにくい環境になっています。 何かで連携をとる際にも、互いの認識を揃えてからスムーズに進行できるよう心がける姿勢がチーム全体に浸透しています。Slackには「ズレ埋め」の状況を伝えるためのSlackのスタンプも用意されていて、日常的に活用されています。 課題を一人で抱え込ませないサポート体制 入社直後は慣れない技術スタックやドメインに戸惑う場面もありました。そんなときありがたかったのは、チームが放置しなかったことです。自分が課題を整理する前に、チームが先回りして状況を確認し声をかけてくれて、すぐに「どうすれば改善・解決していけるか」を一緒に考えるフェーズに入りました。こまめに1on1を設けてもらい、自分の状態を定期的に言語化する場があったのですが、これがかなり効きました。掘り出し始めると課題がボロボロ出てきましたが、今まで顕在化していなかった問題を認知でき、良い取り組みになったと感じています。 また、そこからは毎週の振り返りで内省のサイクルを回すようにもしました。その週に何があって、自分はどう感じて、何がうまくいかなかったのかを細かく言語化する。地味ですが、これを続けることで自分の問題のパターンが見えてきます。やったことはシンプルで、問題を洗い出して、それぞれに対するアプローチをブレイクダウンしていっただけです。魔法みたいな解決策があったわけではなく、泥臭く一個ずつ潰していきました。 技術・開発体制の特徴 フロントエンド・バックエンドの垣根がない開発体制 私たちのチームでは、一つの機能をフロントエンドからバックエンドまで通して実装します。「フロントエンドエンジニア」「バックエンドエンジニア」という分業ではなく、プロダクトエンジニアとして機能全体に向き合うスタイルです。 私は前職ではフロントエンドを専門にしていたため、バックエンドは2〜3年まともに触っておらず、RubyもRailsもほぼ浦島太郎状態でした。「いけるでしょ」と思っていた矢先、いざ取り組んでみると思ったより読めないことに気が付きました。 これはまずいということで、チームメンバーにサポートを受けながらキャッチアップの日々が始まりました。SmartHRにはRubyやRails入門のためのカリキュラムも用意されており、体系的に学び直すことができて効率よくキャッチアップすることができました。おかげさまで、今ではかなり読み書きできる状態になっています。 特定の領域に閉じないことで、機能全体を見通した設計判断ができるようになります。フロントエンドだけを見ていた頃には気づけなかった観点が得られる実感があり、大変ではあるものの、エンジニアとしての幅が確実に広がっていると感じます。 大規模なコードベースと丁寧なコードレビュー SmartHRのコードベースは、前職のスタートアップと比べて規模が桁違いでした。前職では0→1の開発が中心で、コードベースは比較的コンパクトでした。改修時の影響範囲もだいたい把握できていて、「ここを変えたら何に影響するか」は感覚でわかることも多かったです。 一方、SmartHRでは影響範囲の調査だけでも結構な労力が必要です。ドメインの複雑さも相まって、一つの変更がどこに波及するのか丁寧に追っていかないと痛い目を見ます。実際に見ました。痛かった。 コードレビューもかなりしっかり行われており、コードに対する自分の理解が少しでも足りていないとすぐに顕在化します。裏を返せば、レビューを通じて理解の甘さに気づける機会でもあり、コードベースへの解像度を上げていく良い糧になっています。この大規模なコードベースに対して、AIをどう活用すれば効率良くキャッチアップできるかを考えることも、良いキッカケになっています。 ドメイン知識にアクセスしやすい環境 先述のとおり、勤怠ドメインは非常に複雑です。しかし、チームにはその複雑さに対応するための環境が揃っています。 まず、ドメインエキスパートへの質問はSlackで気軽にできます。「こういう場合はどうなるんですか?」といった疑問をその場で投げかけられるので、理解が曖昧なまま実装を進めてしまうリスクが減ります。 新機能の実装など、複雑なドメインが絡む場面ではドメインエキスパートによる共有会が開かれることもあり、チーム全体で認識を揃えてから開発に入れる体制になっています。 また、ドキュメントも充実しています。用語集、業務フロー、法令の解説など、ドメイン理解に必要な資料が体系的に整備されています。 入社直後、多数ある労働時間制の仕組み・違いが分からず戸惑いましたが、用語集などで基本概念を理解し、実装に必要な理解を得ることができました。こうした資料のおかげで、後からジョインしたメンバーでも自力でキャッチアップを進められます。 ドメインの複雑さそのものは変わりませんが、「わからないことをわからないままにしない」ための手段がチームとして整っていることが、大きな強みです。 AIを活用した開発文化 勤怠プロダクトの開発組織では、個人個人がAIを活用して開発業務に取り組んでいるのはもちろんのこと、組織としてもAI活用を推進する体制が整っています。その中の1つのチームは、AIネイティブな開発プロセスの実現を専門的に担っています。開発プロセスを可視化し、どの工程をAIネイティブ化できるかを検証したうえで、成果を他のチームへ横展開しています。すでに開発ライフサイクルにAIが組み込まれた業務フローもでき始めており、試行錯誤の段階から実運用のフェーズに移りつつあります。 AIと人間の役割をどこで分けるかについて、明確なルールがあるわけではありません。ただ、チームの基本的な考え方として「どうすればAIに一任できるか」を起点にしています。人間がやるべきことを先に決めてAIに残りを渡すのではなく、まずAIに任せることを前提に考え、人間の判断が本当に必要な部分を見極めていく。この発想の順序が、AI活用を自然に進められている要因の一つでしょう。 半年を振り返って 入社前、勤怠ドメインの理解にはかなり時間がかかるだろうと覚悟していました。法令・制度・就業規則が複雑に絡み合うドメインなので、一人前に議論できるようになるまで相当かかるだろうと思っていたのが正直なところです。 しかし、先述したドメインエキスパートへの相談のしやすさやドキュメントの充実など、チームのサポート体制のおかげで、想像していたよりもずっと早くキャッチアップが進みました。今では仕様検討の場でも自分の意見を出して議論に参加できており、入社前に抱いていた不安はすっかり解消されました。 ドメイン理解も技術面も、まだまだこれからではありますが、半年前には見えていなかった景色が見えるようになってきた実感があります。 まとめ 約半年このチームで過ごしてきて感じる魅力は、「複雑なドメインに対して、メンバーが主体的に向き合っているチーム」だということです。Whyを大事にする開発姿勢、フロントエンド・バックエンドの垣根を越えて機能全体に向き合うスタイル、ドメイン知識への投資、AI活用の推進。そのすべてに、一人ひとりが自分ごととして取り組む姿勢が貫かれています。 このチームで活躍しているメンバーに共通しているのは、主体性を持って動いていることです。 複雑なドメインを自ら理解しにいき、担当範囲を越えて機能全体に向き合う。そうした姿勢がチーム全体の推進力になっています。 勤怠ドメインという複雑で大きなプロダクトの開発を最前線で経験できる機会はなかなかありません。ドメインの奥深さ、チームの文化、技術的な挑戦、どれをとっても貴重な経験が得られる環境です。 私自身、この環境でもっと力をつけて、チームとプロダクトの成長に貢献していきたいと思います。 We Are Hiring! SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です! 少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう! hello-world.smarthr.co.jp
こんにちは、SmartHR でプロダクトエンジニアをしている@nabeliwoです。 SmartHRは、2026年5月22日〜23日に開催された「TSKaigi 2026」にSilverスポンサーとして協賛し、9名が参加し、1名が登壇しました。 この記事では、イベントの模様と合わせて、スポンサー活動から登壇までの経験をお届けします! TSKaigi 2026とは TSKaigi 2026は、TypeScriptに関する技術カンファレンスです。今年のミッションも昨年から引き続き「学び、繋がり、型を破ろう」でした。 2026.tskaigi.org Silverスポンサーとして協賛しました SmartHRはTSKaigi 2026にSilverスポンサーとして協賛しました。 TypeScriptはSmartHRのフロントエンド開発において重要な技術であり、このコミュニティの発展に貢献したいという思いから協賛させていただきました。 Silverスポンサーは参加者に配布されるトートバッグにノベルティを封入できるため、フロントエンドエンジニア向けの採用チラシとクリアファイルを制作しました。 その際に、クリアファイルにチラシを挟んだ状態で送付する必要があったため、900部のクリアファイル全てにチラシを一枚一枚手作業で挟み込む作業をしたのですが、たまに雑談をしつつ黙々と作業する時間が思いの外楽しかったなと、今回のイベントの一つの思い出になりました。 封入作業の様子 また、今回は学生支援スポンサーもさせていただきました。 学生支援を担当してくれたメンバーからは、「地方からTSKaigiに参加されていた学生4名と一緒にランチをして、TSKaigiに興味を持っている背景、個人プロジェクトや研究の内容などについて話をしました。美味しいご飯を食べながら雑談をし、実際のSmartHRの社員としての責任などを話すことができ、貴重な時間でした!」とコメントをもらっており、とても良い機会をいただけたと感じています。 本編への参加 当日は9名がイベントに参加しました。 タイミングが合わず全員は揃いませんでしたがSmartHRポーズで 当日企画としてNFCカードを配付して各自好きなURLを登録することでコミュニケーションを促進したり、ネイルやフェイスペイントのブースが設置されていてお祭り感を楽しめたり、はたまたマッサージコーナーを設けて疲れを癒したり、とてもホスピタリティ溢れる素敵なイベントだと感じました。 また公式サイトでは事前に自分の見るセッションのスケジュールを組むことができ、会場であるベルサール羽田空港に因んで飛行機の運行ダイヤのような見た目でシェアできる機能もあったり、よく作り込まれたサイトになっていました。 当日のセッションに関しては、今年はBranded Typesの話が多かったように感じました。 Branded Types自体は去年から採用事例を各所で見るようになった印象でしたが、今年はそれが運用に乗ってメリット・デメリットも含めて話せるくらいにはこなれていったのかもしれないなと思いました。 個人的に特に印象に残ったのはlacolacoさんの「いつテストを書くか?―ソフトウェア開発における安心と不安について考える」とminako-phさんの「柔軟なPDFレイアウトエディタを支える型システム設計 — Discriminated UnionとConditional Typeの実践」というセッションでした。 どちらも豊富な経験と深い考察からしか出てこないような、とても勉強になる内容でした。 docs.google.com speakerdeck.com またブーススペースに関して、多くの企業が趣向を凝らした企画をされていて、どのブースも人で溢れていてとても熱気がありました。 個人的には普段の開発においてAIレビューでとてもお世話になっているCodeRabbitさんのブースでキーキャップをもらえたことが嬉しかったです。 登壇レポート 僕は2日目に「React の props は値の集合ではない — UI の状態を宣言するコンポーネント設計」というタイトルで10分のセッションを行いました。 Reactを使った開発を行う中でコンポーネントのインターフェースについて日々考えていたことをまとめています。 nabeliwo.github.io TypeScriptという言語として深い話をするわけではなかったので、発表前は自分の話す内容が参加者の期待に沿うのかわからず不安だったのですが、終わってみると良い反応をいただけたので安心しました。 どんな話であれ、自分の経験に基づいて生み出された話であれば誰かしら刺さる人はいるものだなと思えました。 緊張しました! まとめ 個人としては初回のTSKaigiから数えて3回目の参加だったのですが、年々カンファレンスの質が上がっており、TSKaigi運営の皆様には尊敬の念を禁じ得ません。 セッション内容も言語自体を深堀る好奇心をくすぐる話から、明日の業務からすぐに使える実践的な知識までとても幅広く、多くの学びと刺激を得られました。 改めて、TSKaigi運営の皆さま、参加者の皆さま、ありがとうございました! 次回こそは弊社もブースを出すぞ……!(2年連続落選しました) 事後勉強会を開催します 最後に告知ですが、2026年6月25日(木)にSmartHR主催で「TSKaigi 2026事後勉強会」を開催します。 TSKaigi 2026に参加して「もっと語り合いたい」と感じた皆様、まだまだ参加者を募集しておりますのでぜひお申込みください! smarthr.connpass.com
こんにちは。2025年に新卒 0 期生として SmartHR に入社したプロダクトエンジニアのかずえもんです。SmartHR の様々なプロダクトの中で、私は届出書類機能の開発チームに所属しています。 届出書類機能は SmartHR の中でも歴の長いプロダクトで、状態管理ライブラリには当時主流であった Redux が採用されています。入社するまで Redux を触ったことがなかった私が、Redux を通じて JavaScript の比較にまつわる落とし穴に遭遇した話をします。 Redux とセレクター: state の基礎 Redux はアプリケーション全体の状態(state)を管理するためのライブラリです。state は唯一のグローバルなオブジェクトとして管理され、コンポーネント側から必要な値を取り出して利用します。 state から値を取り出す関数をセレクターと呼びます。React Redux が提供する useSelector フックにセレクターを渡すことで、コンポーネントは state の変化に応じて自動的に再レンダリングされます。 // state 全体を受け取り、必要な値だけを返すセレクター const selectUsername = (state: RootState) => state.user.name // コンポーネント内でセレクターを渡して値を取り出す const username = useSelector(selectUsername) useSelector は、セレクターの返り値が前回と変化したときだけ再レンダリングを実行します。返り値の変化はデフォルトでは === で比較されます。ここで注意が必要なのは、セレクター関数の中で配列やオブジェクトを新規に作成して返す場合です。 例えば以下のようなセレクターの場合、state が変わっていなくても呼び出すたびに新しいオブジェクトが生成されるため、=== で比較すると常に「変化あり」と判断されてしまい、name と age が変わっていなくても再レンダリングが発生してしまいます。 const selectUser = (state: RootState) => ({ name: state.user.name, age: state.user.age }) // 不要な再レンダリングが発生してしまう const { name, age } = useSelector(selectUser) そこで利用できるのが、React Redux が提供する比較関数 shallowEqual です。useSelector の第2引数に shallowEqual を渡すことで、オブジェクト全体の参照ではなく各プロパティの値を個別に === で比較するようになります。すなわち name か age の値が変わっていなければ再レンダリングは実行されません。 const selectUser = (state: RootState) => ({ name: state.user.name, age: state.user.age }) // shallowEqual を設定すると再レンダリングを抑制できる const { name, age } = useSelector(selectUser, shallowEqual) 常に shallowEqual を使いたい場合は、React Redux の公式ドキュメントでも紹介されている useShallowEqualSelector というカスタムフックを作成しておくと便利です。 import { useSelector, shallowEqual } from 'react-redux' export function useShallowEqualSelector(selector) { return useSelector(selector, shallowEqual) } プロダクト内でもこのカスタムフックを利用しており、次のような書き方で複数の値をまとめて取り出していました。 const { docIds, status } = useShallowEqualSelector((state: RootState) => ({ docIds: getActiveDocIds(state.docGroup, state.doc.currentDoc?.docMasterRevisionId), status: getDocGroupStatus(state.docGroup), })) Redux の warning が教えてくれた 書類の詳細画面に関するレビューをしていたある日、ブラウザの開発者コンソールを眺めていたら warning が出ていることに気がつきました。 Selector unknown returned a different result when called with the same parameters. This can lead to unnecessary re-renders. 「同じインプットに対して、セレクターが異なる結果を返している」という内容です。「shallowEqual を使っているのになぜ?」と疑問に思い、調査を開始しました。 鍵は shallowEqual 関数の比較ロジックにありました。 shallowEqual が渡された配列やオブジェクトを比較するとき、それぞれのプロパティの値は === で比較されます。次の表は、 shallowEqual に対して空の配列、同じ値の入った配列、配列が入ったオブジェクトを渡したときの比較結果をまとめたものです。 式 shallowEqual の結果 shallowEqual([], []) true shallowEqual([1], [1]) true shallowEqual({ ary: [] }, { ary: [] }) false [] や [1] 同士を shallowEqual で比較すると true になります。前述の通り shallowEqual は渡された配列・オブジェクトをプロパティごとに見て === で比較するためです。つまり shallowEqual([1], [1]) では 1 === 1 が比較されていることになります。 しかし { ary: [] } のようにオブジェクトのプロパティが配列の場合は false になります。プロパティ ary の値を === で比較するとき、それぞれの [] は別のインスタンスであるため参照が異なると判断されるためです。 useShallowEqualSelector でオブジェクトにまとめて複数の値を取り出す手法ではまさにこれが落とし穴になります。問題になっていた getActiveDocIds の実装を見てみましょう。 export const getActiveDocIds = (state: DocGroupState, docMasterRevisionId: string | undefined) => { const docsPerdoc = state.current.docGroup?.docsPerDoc.find((doc) => doc.docMasterRevisionId === docMasterRevisionId) return docsPerdoc?.docIds || [] } find の結果が存在しない場合に [] を返しています。このリテラル [] は呼び出すたびに新しい配列インスタンスを生成します。つまり、各値を取り出すセレクター関数の中にこのような呼び出すたびに新しい配列を生成するものが含まれていると、そのプロパティは常に === で false と判断されてしまい、shallowEqual を使っていても docIds プロパティは常に「変化あり」と判断されて再レンダリングが走り続けていたのです。 createSelector でセレクターをメモ化する 呼び出すたびに新しい配列を生成してしまう場合、Reselect が提供する createSelector の出番です。createSelector は計算をメモ化するための関数で、インプットが変わっていなければ再計算をスキップし、前回と同じ参照を返し続けます。createSelector は Redux Toolkit 経由でも利用できます。 基本は useSelector に渡す関数そのものに使われることが多いですが、今回のようにオブジェクトにまとめて複数の値を取り出す手法における個別の関数に対しても適用できます。getActiveDocIds をメモ化することで、インプットが変わらない限り同じ配列の参照を返し続けるようになり、再レンダリングを防げます。 import { createSelector } from '@reduxjs/toolkit' export const getActiveDocIds = createSelector( [ (state: DocGroupState) => state.current.docGroup?.docsPerDoc, (_: DocGroupState, docMasterRevisionId: string | undefined) => docMasterRevisionId, ], (docsPerDoc,<span cla