JavaScript(ES)のUIフレームワークの一つ
事実上のWebデファクトスタンダード
1. IE/Netscapeブラウザ2強時代
2. JavaScript/VBScriptの共存時代
3. スクリプト無効化時代
4. MS ActiveX/Adobe Flashの終焉とともにJavaScript一強時代
💡ネイティブなままでは品質の担保が難しい
多様な規格/フレームワークを生み出してきた
Node.js + npx + Babel + TypeScript + Promise + ... + React
💡かつてはクラスコンポーネントが存在していた
💡今後は関数コンポーネントを使う
const NumberList = (props) => {
const numbers = props.numbers
const listItems = numbers.map((number) => <li>{number}</li>)
return (
<ul>{listItems}</ul>
)
}
const numbers = [1, 2, 3, 4, 5]
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<NumberList numbers={numbers} />)
💡親コンポーネントのDOMの外側に子コンポーネントを描画することもできることはできる(portal/bubbling)
Document Object Model
💡元々ReactではJSXで書くことが一般的だった
const Example = () => {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count+1)}>
Click me
</button>
</div>
)
}
(props) => render
sum :: [Int] -> Int
max :: Ord a => [a] -> Int
length :: [a] => Int
map :: (a -> b) -> [a] -> [b]
all :: (a -> Bool) -> [a] -> Bool
Pages | ひとつのページ全体 |
---|---|
Templates | ページ構成の基本となる型 |
Organisms | コンポーネントの組み合わせ |
Molecules | 複合することで意味を持つコンポーネント |
Atoms | 最小単位のコンポーネント |
💡Organisms/Molecules/Atomsは純粋コンポーネントであることが理想的
💡品質のために自明な性質にする
const Page = (
<>
<Collapse><p>...</p></Collapse>
<Collapse><p>...</p></Collapse>
<Collapse><p>...</p></Collapse>
<Modal>
<Modal.Header>
<strong>Title</strong>
</Modal.Header>
<Modal.Body>
<h4>Some messages</h4>
</Modal.Body>
<Modal.Footer>
<Button>...</Button>
<Button>...</Button>
</Modal.Footer>
</Modal>
</>
)
コンポーネントを小さくする
const ModalHeader = () => (
<Modal.Header>
<strong>Title</strong>
</Modal.Header>
)
const ModalBody = () => (
<Modal.Body>
<h4>Some messages</h4>
</Modal.Body>
)
コンポーネントを階層化
const ButtonCancel = () => <Button>...</Button>
const ButtonConfirm = () => <Button>...</Button>
const ModalFooter = () => (
<Modal.Footer>
<ButtonCancel />
<ButtonConfirm />
</Modal.Footer>
)
小さなコンポーネントで構成する
const ConfirmationModal = () => (
<Modal>
<ModalHeader />
<ModalBody />
<ModalFooter />
</Modal>
)
ページ構成も小さく保つ
ページ構成も小さく保つ
const CollapseStep1 = () => (
<Collapse><p>...</p></Collapse>
)
const CollapseStep2 = () => (
<Collapse><p>...</p></Collapse>
)
const CollapseStep3 = () => (
<Collapse><p>...</p></Collapse>
)
const Page = (
<>
<CollapseStep1 />
<CollapseStep2 />
<CollapseStep3 />
<ConfirmationModal />
</>
)
💡状態をどこに持たせることが適切かは常に考える
import Button from 'react-bootstrap/Button'
import styled from 'styled-components'
const StyledButton = styled(Button)`
width: 50%;
height: 100%;
`
const Example = () => {
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count+1)}>
Click me
</button>
</div>
)
}
💡useStateだけで状態管理できる
💡ReduxでDispatch/Action/Reducer/Storeを用意して、this.state.countを使わなくてよくなった
複数の状態を単一のStateで管理するときは、その方法が適切かを考える
const [signInFlow, setSignInFlow] = useState({
isAuthenticated: false,
isLocked: false,
isTemporaryPassword: false,
isError: false
})
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLocked, setIsLocked] = useState(false)
const [isTemporaryPassword, setIsTemporaryPassword] = useState(false)
const [isError, setIsError] = useState(false)
// 値が変化しない場合は呼び出さない
if (currentState !== nextState) {
setCurrentState(nextState)
}
useEffect(() => {
document.title = `You clicked ${count} times`
})
useEffect(() => {
ChatAPI.subscribeFriendsStatus()
return () => {
ChatAPI.unsubscribeFriendsStatus()
}
})
💡マウント時にsubscribeして、アンマウント時にunsubscribeすることができる
useEffect(() => {
document.title = `You clicked ${count} times`
}, [count])
💡countが変更された場合にのみ再評価
useEffect(() => {
const filtered = items.filter(isSatisfied(deposit))
}, [items, deposit])
// items/depositがpropsのとき、変化する度に再評価される
const filtered = items.filter(isSatisfied(deposit))
💡useEffectとProps渡しの違いは?
本来、再評価が必要な依存が漏れているため、値が更新されない、古い値を参照するなど意図しない動作を引き起こす
useEffect(() => {
getSomething(myId)
}, [])
無関係な値の更新時に再評価され、計算コストだけがかかる
useEffect(() => {
getSomething(myId)
}, [workspaceId])
useEffect(() => {
arrayCorpIds.map(corpId => {
getSomething(corpId)
})
}, [arrayCorpIds])
useEffect(() => {
arrayCorpIds.map(corpId => {
getSomething(corpId)
})
}, [arrayCorpIds])
if (currentState !== nextState) {
setCurrentState(nextState)
}
type State = {
isAuthenticated: boolean
isLocked: boolean
isTemporaryPassword: boolean
isError: boolean
}
type Action =
| { type: 'authenticate' }
| { type: 'lock' }
| { type: 'setTemporaryPassword' }
| { type: 'setError' }
| { type: 'reset' }
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'authenticate':
return { ...state, isAuthenticated: true }
case 'lock':
return { ...state, isLocked: true }
case 'setTemporaryPassword':
return { ...state, isTemporaryPassword: true }
case 'setError':
return { ...state, isError: true }
case 'reset':
return { isAuthenticated: false, isLocked: false,
isTemporaryPassword: false, isError: false }
default:
return state
}
}
// 認証完了時に呼び出す
dispatch({ type: 'authenticate' })
// ロック状態だったときに呼び出す
dispatch({ type: 'lock' })
// 仮パスワード状態だったときに呼び出す
dispatch({ type: 'setTemporaryPassword' })
// エラー時に呼び出す
dispatch({ type: 'setError' })
// サインアウトに呼び出す
dispatch({ type: 'reset' })
例)ログイン後の名前表示、言語切り替えで変わる文言、など
💡計算コストの方が低い場合はmemo化しない
propsから導出された変数をどう扱うか
💡認証情報、言語設定、ダークモードなどグローバルにどのページでも利用するものに限定して利用する
Redux独自のカスタムフック拡張
💡どれでも同じようなことを実現できる
💡チームの場合は方針やルール決めが大事
React + React Hooks + Redux + JSX + TypeScript + Promise + Redux-Saga + react-bootstrap + Babel + Webpack + terser + Node.js + npx + ESLint + create-react-app + ...
💡GraphQL使うならapollo-clientやamplifyなんかも・・