React-dva入门

529 阅读2分钟

React 组件

两种常用的定义方式

  • Stateless Functional Component (pure function)
function App(props) {
  function handleClick() {
    props.dispatch({ type: 'app/create' });
  }
  return <div onClick={handleClick}>{props.name}</div>
}
  • Class
class App extends React.Component {
  handleClick() {
    this.props.dispatch({ type: 'app/create' });
  }
  render() {
    return <div onClick={this.handleClick.bind(this)}>{this.props.name}</div>
  }
}

React生命周期

react 生命周期.png

JSX语法

  • 组件嵌套

组件名称首字母必须大写

<App>
  <Header />
  <MainContent />
  <Footer />
</App>
  • className
<div className='myClass'>
...
</div>
  • 内联样式添加
<div style={{ height:'20px', color:'#000000', fontSize:'16px' }}>
...
</div>
  • Javascript 表达式
<span> { this.props.text } </span>

<span> { this.props.text ? this.props.text : '' } </span>

<div>
{
    this.props.todos.map((todo, i) => <li key={i}>{todo}</li>) 
}
</div>

React基本的父子组件传值方式

class ParentComponent extends React.Component {
  render() {
    return(
        <div>
            <span>some text...</span>
            <SonComponent name="myName"/>
        </div>
    ) 
}

function SonComponent(props) {
  return <div>{props.name}</div>
}

Router

  • 项目目录中的router.js 返回一个组件并且export
function RouterConfig({ history, app }) {
    ...
    return (
        <LocaleProvider locale={zhCN}>
            <Router history={history}>
                <Switch>
                    {/* <Route path="/user/login" component={Login} /> */}
                    <Route path="/" render={props => <BasicLayout {...props} {...passProps} />} />
                </Switch>
            </Router>
        </LocaleProvider>
    );
}

export default RouterConfig;

index.js(systemAdmin.js)

import dva from 'dva';
import { browserHistory } from 'dva/router';
import createLoading from 'dva-loading';

// 1. Initialize
const app = dva({
    onError(e) {
        if (e.handleError) {
            e.handleError();
        } else {
            throw e;
        }
    },
    history: browserHistory,
});

// 2. Plugins
app.use(createLoading());

// 3. Model
app.model(require('./models/newTenant'));
...

// 4. Router
app.router(require('./router'));

// 5. Start
app.start('#root');

Routes

  • 大致结构
import ...

class MyComponent extends React.Component{
    componentDidMount() {
        ...
    }

    render(){
        return(
            ...
        )
    }
}

export default connect(({ mycomponent }) => ({
    mycomponent,
}))(MyComponent);
  • 使用装饰符形式的结构
import ...

@connect(({ mycomponent }) => ({
    mycomponent,
}))
export default class MyComponent extends React.Component{
    componentDidMount() {
        ...
    }

    render(){
        return(
            ...
        )
    }
}

Model

  • 大致结构
import ...

export default {
    namespace:...,
    state:{
        // 状态
        ...
    },
    reducers:{
        // 改变state的方法
        ...
    },
    effects:{
        // 异步方法(多用于请求接口)
        ...
    },
    subscriptions:{
        // 订阅
        ...
    }
}

reducers

  • 常用
// 直接覆盖state的某个直接子属性
overrideStateProps(state, { payload }) {
    return {
        ...state,
        ...payload,
    };
},
// 改变state的第二层子属性
updateStateProps(state, { payload }) {
    const { name, value } = payload;
    return {
        ...state,
        ...{ [name]: { ...state[name], ...value } },
    };
},
  • 使用方法举例
this.props.dispatch({
    type: 'newTenant/overrideStateProps',
    payload: {
        checkTenantData: [],
    },
});
this.props.dispatch({
    type: 'machineList/updateStateProps',
    payload: {
        name: 'listParams',
        value: {
            search_text: 'custom text',
        },
    },
});

effects

* fetchLaunchDetail({ payload }, { call, put }) {
    const fetchResult = yield call(
        request,
        api.fetchLaunchDetail(payload),
    );
    if (!fetchResult.XCmdrCode) {
        yield put({
            type: 'overrideStateProps',
            payload: {
                appLaunch: fetchResult.XCmdrResult,
            },
        });
    } else {
        Message.error('错误')
    }
},
call

call用于调用异步逻辑,支持 promise 。

put

用于触发 action 。( 调用 reducer 或 effects )

select

用于从 state 里获取数据。

const todos = yield select(state => state.todos);

subscriptions

Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。

Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。

数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。

  • 监听history
setup({ dispatch, history }) {
    history.listen((location) => {
    const match = pathToRegexp('/map/machineMapInProject/:id').exec(location.pathname);
        if (match) {
            const id = match[1];
            dispatch({
                type: 'updateStateProps',
                payload: {
                    name: 'listParams',
                    value: { project: id },
                },
            });
        }
    });
},
  • 监听key事件
import key from 'keymaster';
...
app.model({
  namespace: 'count',
  subscriptions: {
    keyEvent(dispatch) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  }
});

SaaS-Model && Services

将Dva中的 fetch 方法进行层层封装, 被model层调用进行网络请求, 然后将请求返回的值传回model层.

  • 需要定义 请求路径(url) 和 请求参数(options)

以antdpro模板项目为例

static uploadExcel(params) {
    return request({
        url: `${apiHostMap.CM_HOST}/import/machines/excel`,
        method: 'POST',
        params: params,
        headers: 'multipart/form-data',
    });
}