ここまでは新しく書く話でした。この回は逆で、既にあるコードを読む話です。
案件に入って最初に開くファイルが、このコースで習った形とは限りません。Reactは2013年からあり、フックが入ったのは2019年です。それ以前の書き方が、いまも大量に動いています。
書けるようになる必要はありません。読めれば十分です。
クラスコンポーネント
いちばんよく出会います。
class BirdCard extends Component {
state = { count: 0 }
componentDidMount() {
document.title = this.props.bird.name
}
componentDidUpdate(prevProps) {
if (prevProps.bird.id !== this.props.bird.id) {
document.title = this.props.bird.name
}
}
componentWillUnmount() {
document.title = 'しまちゃん'
}
handleClick = () => {
this.setState((prev) => ({ count: prev.count + 1 }))
}
render() {
return <button onClick={this.handleClick}>{this.props.bird.name} / {this.state.count}</button>
}
}対応はこうです。
| クラス | いまの書き方 |
|---|---|
this.props.bird | propsの分割代入({ bird }) |
state = { count: 0 } | useState(0) |
this.setState({ count: 1 }) | setCount(1) |
this.setState((prev) => …) | setCount((prev) => …)(同じ形) |
componentDidMount | useEffect(() => { … }, []) |
componentDidUpdate | useEffect(() => { … }, [依存]) |
componentWillUnmount | useEffectのreturnで片づけ |
render() | 関数のreturn |
stateが2つの形で書かれているのも見ます。
constructor(props) {
super(props)
this.state = { count: 0 }
this.handleClick = this.handleClick.bind(this) // ← thisの束縛
}bindが出てくるのは、メソッドの中のthisが外れるというクラス特有の問題への対処です。フックにはそもそもthisが無いので、この行はまるごと消えます。
HOC(高階コンポーネント)
コンポーネントを受け取って、機能を足したコンポーネントを返す関数です。名前はwith〜で始まるのが慣習でした。
export default withRouter(withTheme(connect(mapStateToProps)(BirdCard)))見た目はぎょっとしますが、やっていることはカスタムフックと同じです。
| HOC | いまの書き方 |
|---|---|
withRouter(Component) | useNavigate() / useParams() |
connect(mapStateToProps)(Component) | useSelector() |
withTheme(Component) | useContext(ThemeContext) |
中身に外から機能を注入するという目的は同じで、包むか中で呼ぶかの違いです。フックのほうが読みやすいのは、どのpropsがどこから来たのかが行として見えるからです。
render props
子として関数を渡して、値を受け取る書き方です。
<MouseTracker>
{({ x, y }) => <p>マウスは {x}, {y} にいます</p>}
</MouseTracker>「値を持っている部品」と「表示する部品」を分けるための工夫でした。これもカスタムフックで置きかえられます。
const { x, y } = useMousePosition()いまでも残っている場面はあります(グラフのライブラリなど、描画する場所を渡したいとき)。見かけたら「関数を子どもとして渡している」と読めれば十分です。
PropTypes
TypeScript以前の型チェックです。
BirdCard.propTypes = {
bird: PropTypes.shape({ name: PropTypes.string.isRequired }).isRequired,
isFavorite: PropTypes.bool,
}
BirdCard.defaultProps = {
isFavorite: false,
}React 19では、これは無視されます。React.PropTypesも削除済みです(実際にtypeof React.PropTypesを見るとundefinedが返ってきます)。関数コンポーネントのdefaultPropsも削除されたので、初期値は引数の既定値で書きます。
function BirdCard({ bird, isFavorite = false }) {旧Context・その他
たまに見る、それ以外のもの。
| 見かけるもの | 何だったか |
|---|---|
<Context.Consumer>{(value) => …}</Context.Consumer> | Contextの読み方(いまはuseContext) |
<Context.Provider value={…}> | React 19からは<Context value={…}>でよい |
this.refs.input | 文字列ref。React 19で削除済み |
ReactDOM.render(…) | React 18より前の起動。いまはcreateRoot(...).render(…) |
UNSAFE_componentWillMount | 廃止予定のライフサイクル。名前のとおり触らない |
React.createFactory | React 19で削除済み |
PureComponent | memoにあたるもの |
案件に入った初日にやること
読み方が分かったうえで、何を先に見るかです。
package.json— Reactのバージョン、ルーター、状態管理、テスト。このプロジェクトの語彙が全部ここにあるsrc/main.tsx(またはindex.js) — 何で包まれているか(Provider・Router・Query)- ルーティングの定義 — 画面の一覧がここに出る
- いちばん大きい画面のファイル1つ — 書き方の癖が分かる
- READMEと、直近のPR数件 — チームの作法が出る
公式ドキュメントも見てみる
ja.react.dev外部サイトレガシー React API – ReactReact公式の「レガシーAPI」。いま非推奨のもの・React 19で削除されたものが一覧になっています。読んでいるコードで見つけた名前を引くのに使えますこのレッスンのまとめ
- この回は書くためではなく読むため。フックは2019年からで、それ以前が大量に動いている
- クラスの
componentDidMount/DidUpdate/WillUnmountは、useEffect1つに対応する - ただし機械的に置きかえると回数が変わる。理由なく書きかえない
bind(this)はクラス特有のthis対策。フックにはそもそもthisが無い- HOC(
with〜)はカスタムフックと同じ目的。包むか、中で呼ぶかの違い - render propsは関数を子として渡す形。いまはカスタムフック
- PropTypesはReact 19で無視される。
defaultPropsは引数の既定値へ - Error Boundaryだけは今もクラス。「古い=動かない」ではない
- 案件の初日は
package.json→main.tsx→ ルーティング → 大きい画面 → READMEの順
次からは、公開の前に踏んでおきたい章です。まずはReactの安全な書き方から。