Reactを学ぶ13

Udemy「フロントエンドエンジニアのためのReact・Reduxアプリケーション開発入門」 作業時間:5時間 経過時間:50時間
すでに知っているセクション等は省略。
アプリケーション実践編
CRUDでまとめた。
Read
curl
CRUD:create,read,update,delete curlコマンドでバックエンドサーバーにリクエストできる。
curl --request POST \
--url '{エンドポイント}' \
--header 'Content-Type: application/json' \
--data '{ "key1:"val hoge","key2:"val fuga",}'
curl --request PUT \
--url '{エンドポイント}' \
--header 'Content-Type: application/json' \
--data '{ "key1:"val hoge","key2:"val hogehoge",}'
curl --request DELETE \
--url '{エンドポイント}' \
--header 'Content-Type: application/json' \
一覧表示
主な登場人物
- applyMiddleware
- redux-thunk lodashを初めて使った。
redux-thunk
actionは通常オブジェクトを返す仕様としている。 redux-thunkを導入すると、非同期処理の算出(関数)を含めて処理できるようになる。 redux-thunkを導入する際は、createStoreの第二引数にapplyMiddlewareを用いて、reducerとの紐付けを行わないといけない。
非同期の場合、以下の構築フローを取る。(ログ確認のチェックポイント) componentsの関数でactionのタイプを受け取れているか actionでapiから結果を受け取れているか reducerでアクションの結果を受け取れているか
connectのログポイントを振り返るべき。
画面遷移
主な登場人物
- BrowserRouter
- Route
- Switch
親コンポーネントでRouteを定義さえしていれば、子コンポーネントでLinkを使えた。
//index.js
import {BrowserRouter,Route,Switch} from 'react-router-dom
<BrowserRouter>
<Switch>
<Route component={hoge}></Route>
</Switch>
</BrowserRouter>
//components/hoge.js
...
class hoge extends Component {
...
render(){
return (
<>
<Link to="/">home</Link>
</>
)
}
}
export default hoge
Create
decorate
componentsで何をインポートするか、謂わば機能を定義していないと、やっぱとっ散らかりそう。
redux-form
作法的な話。
asはエイリアスだったのか。 combineReducersの引数にredux-formを渡さないといけない。
//reducers/index.js
import {reducer as form} from 'redux-form'
...
export default combineReducers({hoge,form})
//components/events_new.js
import {Field,reduxForm} from 'redux-form'
class hoge extends Component {
...
//Fieldコンポーネントの実体。属性を分割代入で渡してエラー表示も行う。
renderField(field){
const {input,label,type,meta:{touched,error}} = field
return (
<div>
<input {...input} placeholder={label} type={type} />
{touched && error && <span>{error}</span>}
</div>
)
}
...
//Fieldコンポーネントを使用し、フォームを作成する。主に属性の定義。
render(){
...
return(
<form onSubmit={handleSubmit(this.onSubmit)}>
<div>
<Field label="Title" name="title" type="text" component={this.renderField}></Field>
<Field label="Body" name="body" type="text" component={this.renderField}></Field>
<div>
<input type="submit" value="Submit" disabled={false}></input>
<Link to="/">cancel</Link>
</div>
</div>
</form>
)
}
//validationの設定
const validate = values => {
const errors = {}
if(!values.title) errors.title = "Enter a title, please."
if(!values.body) errors.body = "Enter a body, please."
return errors
}
//connectの引数にredux-formを渡す。コンポーネントにvalidationを紐付ける。
export default connect(null,mapDispatchToProps)(
reduxForm({validate,form:'eventNewForm'})(EventsNew)
)
}
disable
formのsubmitの管理。
pristine
とsubmitting
というbooleanを返す変数があるらしい。
pristine
は、レンダリング後にtrueを返す。(フィールドにテキストを打つまでは、クリックできない。ただし、validationは見ていないっぽい。)
submitting
は、一度押されたらtrueを返す。
以下のようにdisalbedに渡すと、初回クリックや連打クリックを防止できる。
<input type="submit" value="Submit" disabled={pristine || submitting} />
Delete
任意のidのデータを消すデータフロー。
//components/events_show.js
class EventsShow extends Component{
...
//削除ボタンのイベントハンドラー。「deleteEvent」というcreateActionにidを渡す役割。
async onDeleteClick() {
const {id} = this.props.match.params
await this.props.deleteEvent(id)
this.props.history.push('/')
}
...
}
//actions/index.js
export const DELETE_EVENT = 'DELETE_EVENT'
...
//以下の処理でサーバーからはデータが消える。しかし、読み込まないと更新されない。
export const deleteEvent = id => async dispatch => {
await axios.delete(`${ROOT_URL}/events/${id}${QUERYSTRING}`)
dispatch({type: DELETE_EVENT,id})
}
//reducers/events.js
import { READ_EVENTS,DELETE_EVENT, } from '../actions'
...
//reducerからも、対象のデータを削除し更新してあげることで、メモリの更新が行われる。
export default (events = {}, action) => {
switch (action.type) {
...
case DELETE_EVENT:
delete events[action.id]
return {...events}
...
}
}
Update(Put)
//components/events_show.js
class EventsShow extends Component{
...
//更新時に「putEvent」というcreateActionにvaluesを渡す役割。
async onSubmit(values) {
await this.props.putEvent(values)
this.props.history.push('/')
}
...
}
//actions/index.js
export const UPDATE_EVENT = 'UPDATE_EVENT'
...
export const putEvent = values => async dispatch =>{
//以下の処理でサーバーのデータが更新される。メモリの更新は別途。
const response = await axios.put(`${ROOT_URL}/events/${values.id}${QUERYSTRING}`,values)
dispatch({type:UPDATE_EVENT,response})
}
//reducers/events.js
...
//reducerからも、データを更新してあげることで、メモリの更新が行われる。
export default (events = {}, action) => {
switch (action.type) {
case CREATE_EVENT:
case READ_EVENT:
case UPDATE_EVENT:
const data = action.response.data
return {...events,[data.id]:data}
...
}
}
Material Design
Context
propの参照は親子の関係に依存していた。propブリリング問題。 これを解決する仕組みがある。 reduxのproviderが相当する。 reactも同名・同様の機能を提供している。
//contextを使用するには、以下のように定義しておく必要がある。
//contexts/counter.js
import {createContext } from 'react'
const ConterContext = createContext()
export default ConterContext
//CounterContext.Providerでラップする。また、valueにstateを渡すと子、孫でstateを受け取れる。
//index.js
import CounterContext from './contexts/counter'
...
class App extends React.Component {
constructor(props) {
super(props)
...
this.state = {
count: 0,
increment: this.increment,
decrement: this.decrement
}
}
...
render() {
return (
<CounterContext.Provider value={this.state}>
<Counter />
</CounterContext.Provider>
)
}
}
//CounterContext.Consumerでラップする。関数で記述する。引数でstateを受け取れる。
//components/counter.js
import CounterContext from '../contexts/counter'
const Counter = () => (
<CounterContext.Consumer>
{
({count,increment,decrement}) => {
return (
...
)
}
}
</CounterContext.Consumer>
)
補足
enableReinitialize
誰かがデータ更新をした際、自分の環境では古いデータのままになる場合がある。 これは、メモリが古い状態を保持したままであり、更新されていないことが原因である。 (サーバーからは、更新されたデータがレスポンスされている) データが更新された場合に、都度初期化を行うことを許すプロパティが__enableReinitialize__になる。