The source code for this blog is available on GitHub.

Blog.

Reactを学ぶ13

Cover Image for 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の管理。 pristinesubmittingという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__になる。


More Stories