4/28 讀書會
- redux 溫故知新
- curry & HOC
- redux-form
Redux 溫故知新
- 
	store 
- 
	action 
- 
	reducers 
- 
	connect 

Redux => 狀態管理的框架
store
// @flow
/* eslint global-require: 0 */
import thunk from 'redux-thunk';
import {
  createStore,
  compose,
  applyMiddleware,
} from 'redux';
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
import fetchMiddleware from 'redux-middleware-fetch';
import reducers from './reducers/index';
/* eslint-disable no-underscore-dangle */
const REDUX_DEVTOOLS = window.__REDUX_DEVTOOLS_EXTENSION__;
/* eslint-enable */
export const history = createHistory();
const storeComposeArray = [applyMiddleware(
  thunk,
  routerMiddleware(history),
  fetchMiddleware,
)];
if (REDUX_DEVTOOLS) {
  storeComposeArray.push(REDUX_DEVTOOLS());
}
export const store = createStore(
  reducers,
  {},
  compose(...storeComposeArray),
);
if (module.hot) {
  module.hot.accept('./reducers', () => store.replaceReducer(require('./reducers/index.js').default));
}
像一個 globle State,存放所有狀態的地方
抱歉字太多 我們直接來看重點
import reducers from './reducers/index';export const store = createStore(
  reducers,
  {},
  compose(...storeComposeArray),
);
-  index.js 使用 combineReducers
 把所有 reducers 綁成一個 reducer
- 引入 index.js (等於引入所有 reducers)
- 引入 reducers 然後 createStore
- export 成 store 然後給 entrypoint使用
entrypoint 是啥 ??
import React from 'react';
import ReactDOM from 'react-dom';
import {
  store,
  history,
} from './store.js';
import App from './App.jsx';
(async () => {
  const root = document.getElementById('root');
  if (root) {
    ReactDOM.render(
      <App store={store} history={history} />,
      root,
    );
  }
})();
action
import { API_REQUEST } from 'redux-middleware-fetch';
export function fetchAllRedEnvelopes() {
  return {
    [API_REQUEST]: {
      types: [
        ALL_RED_ENVELOPES_FETCHED,
      ],
      auth: true,
      method: 'GET',
      entrypoint: '/Activity/LuckyMoneyDetail',
    },
  };
}傳遞資訊給 reducer
- type
- 其他資訊
type 本為字串,改成常數方便引入
reducers
export default (state: State = {
  activities: [],
  allRedEnvelopes: [],
  redEnvelopesPress: {},
  ......//more initial state
}, action: any) => {
  switch (action.type) {
    case ALL_RED_ENVELOPES_FETCHED:
      return {
        ...state,
        allRedEnvelopes: action.data.map(redEnvelope => ({
          id: redEnvelope.activityId,
          title: redEnvelope.activityName,
          remark: redEnvelope.activityRemark,
        })),
      };
   ......//more case
})(previousState, action) => newState
Pure Function
connect
// @flow
import React, { PureComponent } from 'react';
import radium from 'radium';
import { connect } from 'react-redux';
import {
  bindActionCreators,
  compose,
} from 'redux';
import * as ActivityActions from '../../actions/Activity';
import AnnouncementLine from '../../components/elements/AnnouncementLine.jsx';
import ActivityRedEnvelopeBanner from '../../components/Activities/RedEnvelope/ActivityRedEnvelopeBanner';
const styles = {
  wrapper: {
    width: '100%',
    height: '100%',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-start',
    flexDirection: 'column',
    '@media (max-width: 1023px)': {
      padding: 0,
    },
  },
  announce: {
    flexShrink: 0,
    width: '100%',
    '@media (max-width: 1023px)': {
      display: 'none',
    },
  },
  borderWrapper: {
    margin: '13px auto',
    width: 'calc(100% - 24px)',
    '@media (max-width: 1023px)': {
      width: '100%',
      height: '100%',
      border: 0,
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'flex-start',
      margin: 0,
    },
  },
  titleWrapper: {
    width: '100%',
    height: 70,
    backgroundImage: 'linear-gradient(to bottom, #F5515F 0, #9F041B 100%)',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    position: 'relative',
    '@media (max-width: 1023px)': {
      display: 'none',
    },
  },
  content: {
    width: '100%',
    height: '100%',
    padding: '27px 0',
    borderWidth: '0 12px 12px 12px',
    borderColor: '#9F041B',
    borderStyle: 'solid',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'flex-start',
    flexDirection: 'column',
    overflow: 'auto',
    '@media (max-width: 1023px)': {
      padding: 0,
      borderRadius: 5,
    },
    '@media (max-width: 767px)': {
      border: 0,
    },
  },
  container: {
    width: 960,
    display: 'flex',
    alignItems: 'flex-start',
    justifyContent: 'flex-start',
    flexWrap: 'wrap',
    '@media (max-width: 1278px)': {
      width: 640,
    },
    '@media (max-width: 1023px)': {
      width: 960,
    },
    '@media (max-width: 993px)': {
      width: 640,
    },
    '@media (max-width: 649px)': {
      width: 320,
    },
  },
  textTitle: {
    fontSize: 25,
    fontWeight: 500,
    letterSpacing: 2,
    color: '#ffffff',
  },
};
type Props = {
  fetchAllRedEnvelopes: Function,
  redEnvelopes: Array<RedEnvelopeType>,
};
class ActivityRedEnvelopeList extends PureComponent<Props> {
  componentDidMount() {
    const { fetchAllRedEnvelopes } = this.props;
    fetchAllRedEnvelopes();
  }
  render() {
    const { redEnvelopes } = this.props;
    return (
      <div style={styles.wrapper}>
        <div style={styles.announce}>
          <AnnouncementLine />
        </div>
        <div style={styles.borderWrapper}>
          <header style={styles.titleWrapper}>
            <span style={styles.textTitle}>红包列表</span>
          </header>
          <div style={styles.content}>
            <div style={styles.container}>
              {redEnvelopes.map(redEnvelope => (
                <ActivityRedEnvelopeBanner
                  key={redEnvelope.id}
                  redEnvelope={redEnvelope} />
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }
}
const reduxHook = connect(
  state => ({
    redEnvelopes: state.Activity.allRedEnvelopes,
  }),
  dispatch => bindActionCreators({
    ...ActivityActions,
  }, dispatch),
);
export default compose(
  reduxHook,
  radium,
)(ActivityRedEnvelopeList);
store 改變了,但 view 呢?
用 connect 去取用
- state (資料)
- dispatch (function)
connect
// @flow
import React, { PureComponent } from 'react';
import radium from 'radium';
import { connect } from 'react-redux';
import {
  bindActionCreators,
  compose,
} from 'redux';
import * as ActivityActions from '../../actions/Activity';
class ActivityRedEnvelopeList extends PureComponent<Props> {
  componentDidMount() {
    const { fetchAllRedEnvelopes } = this.props;
    fetchAllRedEnvelopes();
  }
  render() {
   const { redEnvelopes } = this.props;
   ...
    <div style={styles.container}>
      {redEnvelopes.map(redEnvelope => (
        <ActivityRedEnvelopeBanner
          key={redEnvelope.id}
          redEnvelope={redEnvelope} />
      ))}
    </div>
    );
  }
}
const reduxHook = connect(
  state => ({
    redEnvelopes: state.Activity.allRedEnvelopes,
  }),
  dispatch => bindActionCreators({
    ...ActivityActions,
  }, dispatch),
);
export default compose(
  reduxHook,
  radium,
)(ActivityRedEnvelopeList);

curry & HOC

curry ?
f(g(x))
在理解 HOC (High-Order Component)之前,你必須了解 HOF(High-Order Function)
const add = (x, y) => (x + y);
add(9, 8); // 17
// create a high-order function
const createAdder = (a) => (b) => add(a, b);
const add2 = createAdder(9);
add2(8); // 17f(g(x))
再來看看 HOC 的定義
Takes one or more component as arguments
Returns a component as its result
const hoc1 = (Component) => {
 return class extends React.Component {
   constructor(props) {
     super(props);
   }
   
   render() {
     return <Component {...this.props} {...this.state} />
   }
 };
}
const EnhancedComponent = ho1(Component);redux-form
- 為什麼要用 redux-form
- 起手式 - combinereducers
- 建立 form - reduxForm
- 建立 input - Field
- 拿 form 的值 - formValueSelector
- 換新的值 - change
為什麼要用 redux-form
方便表單提交的資料管理
起手式 - combinereducers
往前看
// @flow
import {
  createStore,
  combineReducers,
  compose,
  applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { createBrowserHistory } from 'history';
import {
  connectRouter,
  routerMiddleware,
} from 'connected-react-router';
import Table from './reducers/Table';
export const history = createBrowserHistory();
export default createStore(combineReducers({
  Table,
  form,
  router: connectRouter(history),
}), {}, compose(
  applyMiddleware(
    thunk,
    routerMiddleware(history),
  ),
));
建立 form - reduxForm
import {
  reduxForm,
  Field,
  formValueSelector,
  change,
  SubmissionError,
} from 'redux-form';
class BoogiYan extends Component {
    ...
    
    return (
        ...
        <form>
            ...
        </form>
    )
}
const formHook = reduxForm({
  form: FORM_BOOGIE,
});
export default formHook(BoogieYan)// @flow
export const FORM_PROGRAM = 'FORM/PROGRAM';
export const FORM_PROGRAM_MODAL_CREATOR = 'FORM/PROGRAM_MODAL_CREATOR';
export const FORM_LOGIN = 'FORM/LOGIN';
export const FORM_APPLIMENT_LIST_FILTER = 'FORM/APPLIMENT_LIST_FILTER';
export const FORM_APPLIMENT = 'FORM/APPLIMENT';
export const FORM_APPLIMENT_PROGRAM_PICKER = 'FORM/APPLIMENT_PROGRAM_PICKER';
export const FORM_APPLIMENT_JUDGE_PICKER = 'FORM/APPLIMENT_JUDGE_PICKER';
export const FORM_SCHEDULE_TASK = 'FORM/SCHEDULE_TASK';
export const FORM_SCHEDULE_TASK_PROGRAM_PICKER = 'FORM/SCHEDULE_TASK_PROGRAM_PICKER';
export const FORM_REGISTER = 'FORM/REGISTER';
export const FORM_SCHEDULE_TASK_EXECUTE = 'FORM/SCHEDULE_TASK_EXECUTE';
export const FORM_REVIEW = 'FORM/REVIEW';
export const FORM_OVERVIEW_FILTER = 'FORM/OVERVIEW_FILTER';
export const FORM_OVERVIEW_LIST = 'FORM/OVERVIEW_LIST';
export const FORM_OVERVIEW_WEEK = 'FORM/OVERVIEW_WEEK';
export const FORM_PROGRAM_CONTACTS = 'FORM/PROGRAM_CONTACTS';
export const FORM_EQUIPMENT_CREATE = 'FORM/EQUIPMENT_CREATE';
export const FORM_JUDGE_LIST = 'FORM/JUDGE_LIST';
export const FORM_SCORELIST = 'FORM/SCORELIST';
export const FORM_ADD_NEW_TEACHER = 'FORM/ADD_NEW_TEACHER';
export const FORM_FILTER_TEACHER = 'FORM/FILTER_TEACHER';
export const FORM_SUPERVISOR_RECORD = 'FORM/SUPERVISOR_RECORD';
export const FORM_PROGRAM_TECHNICAL_FIRE = 'FORM/PROGRAM_TECHNICAL_FIRE';
export const FORM_ANNOUNCEMENT = 'FORM/ANNOUNCEMENT';
export default null;
form.js
建立 input - Field
import { Field } from 'redux-form'
class BoogieYan extends Component {
    return (
       <Field
          single
          placeholder="請選擇日期"
          component={DatePicker}
          name="heldAt" />   
    
    )
}基本上就是個
內含input的 Component
除了吃 Component
必須要給 name 去區分它是哪個 input
拿 form 的值 - formValueSelector
import {
  formValueSelector,
  change,
} from 'redux-form';
import { FORM_APPLIMENT } from '../../Constant/form';
const selector = formValueSelector(FORM_APPLIMENT);
class Boogie...
const reduxHook = connect(
  state => ({
    judges: selector(state, 'judges') || [],
  }),
);
export default reduxHook(BoogieYan)'judges' => field name
change
change(form:String,  field:String,  value:String)
 
Saves the value to the field.
const reduxHook = connect(
  state => ({
    programs: selector(state, 'programs') || [],
  }),
  dispatch => ({
    updatePrograms: newPrograms => dispatch(change(FORM_APPLIMENT, 'programs', newPrograms)),
  }),
);4/28 讀書會
By Jay Chou
4/28 讀書會
- 469
 
   
   
  