react-native/reduxでのフロントエンドDDD

react-native/reduxでのフロントエンドDDD:


下記を使用

  • react-native
  • redux
  • typescript
  • immutable.js
  • firebase


構成図



スクリーンショット 2019-01-01 22.30.30.png



パッケージ構成

|--applications 
|  |--App.tsx 
|  |--store.tsx 
|--assets 
|--domains 
|  |--navigation-domain 
|  |  |--nav-container.tsx 
|  |  |--nav-model.tsx 
|  |  |--nav-reducer.tsx 
|  |--profile-domain 
|  |  |--profile-action.tsx 
|  |  |--profile-model.tsx 
|  |  |--profile-reducer.tsx 
|  |  |--profile-thunk.tsx 
|--views 
|  |--atoms 
|  |  |--sample-atom.tsx 
|  |--molecules 
|  |  |--sample-mol.tsx 
|  |--organisms 
|  |  |--sample-org.tsx 
|  |--pages 
|  |  |--sample-page.tsx 
|  |--templates 
|  |  |--footer-tmp.tsx 
|  |  |--header-tmp.tsx 


アーキテクチャ構成


react-component



image.png


  • atomic designで構成します。
  • page, organism, template, molecule, atomの構成となります。
  • 上層から下層の呼び出しのみを許可します。
  • pageはorganism、templateを呼び出すのみ。
  • organismはmoleculeを呼び出すのみ。
  • moleculeはatomを呼び出すのみ。
  • 描写のみを担当して、ビジネスロジックは持ちません。
profile-page.tsx
/** プロフィールpage */ 
export default class ProfilePage extends Component { 
    render() { 
        return ( 
            <Container> 
                <HeaderTmp/> 
                <Content> 
                    <ProfileInputOrg/> 
                </Content> 
                <FooterTmp/> 
            </Container> 
        ); 
    } 
} 
profile-input-org.tsx
interface Props { 
    userId: string, 
    profileModel: ProfileModel, 
    profileFind: Function, 
    profileUpdate: Function, 
    textChange: Function, 
} 
 
/** プロフィールorganism */ 
class ProfileInputOrg extends Component<Props>{ 
 
    componentWillMount() { 
        this.props.profileFind(this.props.userId); 
    } 
 
    render() { 
        return ( 
            <View style={styles.inputPanel}> 
                <Form> 
                    <Item floatingLabel last> 
                        <Label>Self Introduction</Label> 
                        <Input 
                            style={styles.inputArea} 
                            multiline={true} 
                            numberOfLines={5} 
                            onChangeText={(value) => this.props.textChange(value)} 
                            value={this.props.profileModel.getText()} 
                        /> 
                    </Item> 
                </Form> 
                <View style={styles.buttonPanel}> 
                    <Button style={styles.button} 
                            rounded 
                            onPress={() => this.props.profileUpdate(this.props.userId, this.props.profileModel)} 
                    > 
                        <Icon type="FontAwesome" name="save"/> 
                    </Button> 
                </View> 
                <ImageUploadOrg/> 
            </View> 
 
        ) 
    } 
} 
 
const mapStateToProps = (state: any) => ({ 
    userId: state.LoginReducer.getUserId(), 
    profileModel: state.ProfileReducer, 
}); 
 
const mapDispatchToProps = (dispatch: any) => { 
    return { 
        // プロフィール取得 
        profileFind(userId: string) { 
            dispatch(profileFind(userId)) 
        }, 
        // プロフィール更新 
        profileUpdate(userId: string, profileModel: ProfileModel) { 
            dispatch(profileUpdate(userId, profileModel)); 
        }, 
        // テキスト変更 
        textChange(value: string) { 
            dispatch(textChange(value)) 
        } 
    }; 
}; 
 
export default connect(mapStateToProps, mapDispatchToProps)(ProfileInputOrg) 


action

  • 各page, organism, molecule, atomにてdispatchします。
  • api通信が必要のないものに限られます。
profile-action.tsx
export const TEXT_CHANGE = "TEXT_CHANGE"; 
 
/** テキストの変更時のアクションです */ 
export const textChange = (value: string) => { 
    return { 
        type: TEXT_CHANGE, 
        value: value 
    } 
} 


thunks

  • api通信が必要な場合はthunksにて実装します。
  • serverからのレスポンスを待ってからreducerでの処理を行います。
profile-thunks.tsx
export const PROFILE_FIND = "PROFILE_FIND"; 
 
/** FireDb(例)のプロフィールを取得します */ 
export const profileFind = (userId: string) => { 
    return (dispatch: any) => { 
        fireDb 
            .ref(`profile/-${userId}`) 
            .on('value', (snapshot: any) => { 
                dispatch({ type: PROFILE_FIND, payload: snapshot.val()}); 
            }) 
    }; 
} 
 


reducer

  • 各ドメイン毎にreducerが存在します。
  • actionやthunksによりどの処理を振り分けます。
  • componentからactionを介して受け取ったmodel、thunksを介して受け取ったapi のresponse を元に
    新しいdomain modelをcomponentに返却します。
profile-reducer.tsx
/** プロフィール用のreducer */ 
export const ProfileReducer = (profileModel = new ProfileModel(), action: any) => { 
    switch (action.type) { 
        // テキスト変更時 
        case TEXT_CHANGE: 
            return profileModel.changeText(action.value); 
        // プロフィール新規作成時 
        case PROFILE_CREATE: 
            return profileModel.create(action.user); 


domain model

  • immutableなmodelとします。
profile-model.tsx
import {Record} from "immutable"; 
 
/** プロフィール用のモデル */ 
export default class ProfileModel extends Record({ 
    /** 名前 */ 
    name: '', 
    /** 住所 */ 
    address: '', 
    /** 誕生日 */ 
    birthday: '', 
    /** 紹介文 */ 
    text: '', 
}) { 
    public getName(): string { 
        return this.get('name'); 
    } 
    public getAddress(): string { 
        return this.get('address'); 
    } 
    public getBirthday(): string { 
        return this.get('birthday'); 
    } 
    public getText(): string { 
        return this.get('text'); 
    } 
 
    /** テキスト変更 */ 
    public changeText(text: string): ProfileModel { 
        return this.set('text', text); 
    } 
 
    /** 新規作成 */ 
    public create(text: string): ProfileModel { 
        return this.set('name', text) 
            .set('address', text) 
            .set('birthday', text); 
    } 
} 
 
 


image.png


コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)