前言

相对于官方demo,给出自己的理解,
秉承我一贯的作风,没有废话,直接讲代码,看注释,自己体会

React 基础

Cli

npm install create-react-app

创建项目

create-react-app demo

HelloWorld

// HelloWorld组件
// 返回一个标签
class HelloWorld extends Component {
  render() {
    return (
      <div>
        <h1> Hello World</h1>
      </div>
    );
  }
}

// 使用HelloWorld组件
class App extends Component {
  render() {
    return <HelloWorld> </HelloWorld>;
  }
}

JSX 表达式

// 重写HelloWorld组件
class HelloWorld extends Component {
  // 一个函数,接受user返回字符串
  formatName(user) {
    return user.firstName + " " + user.lastName;
  }
  // 一个对象
  User = {
    firstName: "Harper",
    lastName: "Perez",
    icon: "./logo.svg"
  };
  render() {
    // 如果User不为空
    if (this.User) {
      return (
        <div>
          // jsx表达式,将组件属性的值传递给标签
          <img src={this.User.icon} />
          // jsx表达式,调用函数
          <h1> Hello World {this.formatName(this.User)} </h1>
        </div>
      );
    }
    // 如果User为空
    else {
      return <h1>Hello World</h1>;
    }
  }
}

Props

// props用来声明组件里的无状态属性,传进来啥就是啥
class Welcome extends Component{
  render(){
    return (
      <div>
        Welcom {this.props.name}
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
  <Welcome name="ferried"></Welcome>
    );
  }
}

State

class Toogle extends Component{
  constructor(props){
    super(props);
    // 将对象{isToogle:true}放入组件内置对象state中
    this.state =   {isToogle:true}
    // 绑定函数的作用域为组件作用域,以便调用this.setState
    this.handleClick = this.handleClick.bind(this)
  }

  // 单击函数
  handleClick(){
    // 通过点击函数修改state中的值
    this.setState(function(asdf){
        return {isToogle:!asdf.isToogle}
    })
  }

  render(){
    return (
      <div>
      <button onClick={this.handleClick}>
        // 根据state中的值来渲染不同效果的页面
        {this.state.isToogle ? 'ON' : 'OFF'}
      </button>
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
      <Toogle></Toogle>
    );
  }
}

Event

class Popper extends React.Component{
    constructor(){
        super();
        //  组件state有一个name
        this.state = {name:'Hello world!'};
    }
    // this 对应 e,event,放在参数列表末尾
    preventPop(name, e){
        e.preventDefault();
        alert(name);
    }
    render(){
        return (
            <div>
                <p>hello</p>
                // bind(this,this.state.name)
                // this 为 a 标签的 event
                // this.state.name 为本组件的state中存储的name
                <a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
            </div>
        );
    }
}

Switch Render

// 第一个组件
function WarningBanner(props) {
  // 如果warn是空返回空
  if (!props.warn) {
    return null;
  }
  // 否则返回html
  return (
    <div className="warning">
      Warning!
    </div>
  );
}

// 第二个组件
class Page extends React.Component {
  constructor(props) {
    super(props);
    // state存储的showWarning是true
    this.state = {showWarning: true}
    // 函数绑定作用域
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    // 改变state中showWarning属性为!showWarning
    this.setState(prevState => ({
      showWarning: !prevState.showWarning
    }));
  }

  render() {
    return (
      <div>
        // 调用第一个组件并传入第二个组件state中的warning
        <WarningBanner warn={this.state.showWarning} />
        // 通过点击事件改变第二个组件重的state.showWarning控制第一个组件返回空还是html
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

list

function ListItem(props) {
  return <li>{props.value}</li>;
}

function NumberList(props) {
  // 传入一个数字
  const numbers = props.numbers;
  return (
    <ul>
    // 数字迭代
      {numbers.map((number) =>
      // 每迭代一次返回一个ListItem组件
      // 并且传入本次迭代的数字
        <ListItem key={number.toString()}
                  value={number} />

      )}
    </ul>
  );
}
function Blog(props) {
  const sidebar = (
    <ul>
      // 迭代li
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  // 迭代content
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
    // 直接插入生成的html
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];

Form

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    // state中存储连个属性
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };
    // 函数绑定作用域
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  // 参数为表单内容的事件对象
  handleInputChange(event) {
    const target = event.target;
    // 从事件对象里取出值并判断
    // 如果是checkbox赋值true/false
    // 不是checkbox则赋值value
    const value = target.type === 'checkbox' ? target.checked : target.value;
    // 获取表单name属性
    const name = target.name;
    // 改变name的值为value
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            // name为isGoing
            name="isGoing"
            type="checkbox"
            // 选项默认值为state中的isGoing
            checked={this.state.isGoing}
            // 表单发生改变时调用函数
            // name = isGoing , type  = checkbox,所以 name:value = checkbox:true
            onChange={this.handleInputChange} />
        </label>
        <br />
        // 同上,但type不同,name value不同
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

状态提升

// 第一个组件
class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    // 一个函数绑定作用域
    this.handleChange = this.handleChange.bind(this);
  }

  // 传递参数为事件
  handleChange(e) {
    // props有一个函数,参数为事件的值
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    // props有一个属性temperature
    const temperature = this.props.temperature;
    // props有一个属性scale
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>在{scaleNames[scale]}:中输入温度数值</legend>
        // input value接收temperature
        <input value={temperature}
              // 当值发生改变时触发handleChange方法
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

// 第二个组件
class Calculator extends React.Component {
  constructor(props) {
    super(props);
    // 绑定作用域
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    // state两个值
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    // 两个变量接收state里的值
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    // 如果 scale为f,调用一个方法,返回数值给变量,否则直接返回temperature的值
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    // 如果 scale为c,调用一个方法,返回数值给变量,否则直接返回temperature的值
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
      //  调用第一个组件
        <TemperatureInput
        // 第一个组件里props.scale = c
          scale="c"
          // 第一个组件里props.temperature =  celsius
          temperature={celsius}
          //  第一个组件里的onChange里的handleChange 函数中的props.onTemperatureChange 绑定了第二个组件的事件
          onTemperatureChange={this.handleCelsiusChange} />
        // 同上
        //  1.输入值
        //  2.触发第一个组件的onChange中的onTemperatureChange(value)
        //  3.触发第二个组件this.handleFahrenheitChange
        //  4.setState改变属性的值,重新Render
        //  5.判断是C还是F然后接收返回的值
        //  6.第一个组件的value = tempetemperaturer,第二个组件的temperature = 第5步返回的值,从而达到页面效果
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
      </div>
    );
  }
}

事件流转比较绕

React 高阶

JSX

组件渲染原理

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

转化为函数

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

调用

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  // 调用
  return <MyComponents.DatePicker color="blue" />;
}

选择类型

import React from 'react';
import { PhotoStory, VideoStory } from './stories';
// 两个属性
const components = {
  photo: PhotoStory,
  video: VideoStory
};

// 传入storyType判断是PhotoStory还是VideoStory从而渲染不同数据类型
function Story(props) {
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

表达式

// 不废话
function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>even</strong>;
  } else {
    description = <i>odd</i>;
  }
  return <div>{props.number} is an {description} number</div>;
}

传入属性方式

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  // 解构传入
  return <Greeting {...props} />;
}

函数

// 生成html
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    // children为本标签内囊括的所有标签
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    // 生成10次
    <Repeat numTimes={10}>
    // 这里会通过props.children调用到添加进items中
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

PropTypes

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// 组件验证器
Greeting.propTypes = {
  // name为string,更多验证类型请google
  name: PropTypes.string
};

静态类型检查

实际上就是加入TypeScript支持,然后tsc编译

Ref Dom

// ref 意为  dom元素的引用,或者链接到dom上
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

const node = this.myRef.current;

///////////////////////////////////

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建 ref 存储 textInput DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:通过 "current" 取得 DOM 节点
    this.textInput.current.focus();
  }
  render() {
    // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

性能优化

讲的是打包和引入方式

create-react-app

npm run build

直接引入

<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>

webpack

new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: JSON.stringify('production')
  }
}),
new webpack.optimize.UglifyJsPlugin()

剔除es6

// 对象
var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};
// 对象
var createReactClass = require('create-react-class');
// 创建react类
var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // 使用混入
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // 调用混入的方法
  },
  tick: function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for {this.state.seconds} seconds.
      </p>
    );
  }
});
// 渲染
ReactDOM.render(
  <TickTock />,
  document.getElementById('example')
);

剔除JSX

const e = React.createElement;

ReactDOM.render(
  e('div', null, 'Hello World'),
  document.getElementById('root')
);

Context

暂时不理解

Fragments

render() {
  return (
    // 把这些元素声明为一个整体的块元素
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 同样的功能不同的形式,带key
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

Portals

// 第一个参数为react子元素
// 第二个参数为dom元素
ReactDOM.createPortal(child, container)


// 两个dom
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

// 第一个组件
class Modal extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个div domy元素
    this.el = document.createElement('div');
  }

// 添加子节点
  componentDidMount() {
    modalRoot.appendChild(this.el);
  }
// 删除子节点 
  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }
// 获取props.children元素,渲染到el外
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This will fire when the button in Child is clicked,
    // updating Parent's state, even though button
    // is not direct descendant in the DOM.
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <p>
          Open up the browser DevTools
          to observe that the button
          is not a child of the div
          with the onClick handler.
        </p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() {
  // The click event on this button will bubble up to parent,
  // because there is no 'onClick' attribute defined
  return (
    <div className="modal">
      <button>Click</button>
    </div>
  );
}

Error Boundaries

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
// 异常处理  
  handleClick = () => {
    try {
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}

高阶组件

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件


Forwarding Refs

Render Props

Library

Accessibility

Code-Splitting

Strict Mode

React-Router

安装

npm install --save react-router

常规路由

Router组件本身只是一个容器,真正的路由要通过Route组件定义。
import { Router, Route, hashHistory } from 'react-router';

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}/>
  </Router>
), document.getElementById('app'));

子路由

<Router history={hashHistory}>
  <Route path="/" component={App}>
    <Route path="/repos" component={Repos}/>
    <Route path="/about" component={About}/>
  </Route>
</Router>


export default React.createClass({
  render() {
    return <div>
    // 这里给子元素留出位置
      {this.props.children}
    </div>
  }
})

路由传参

<Route component={Inbox}>
  <Route path="inbox/messages/:id" component={Message} />
</Route>

默认路由

<Router>
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="accounts" component={Accounts}/>
    <Route path="statements" component={Statements}/>
  </Route>
</Router>

重定向路由

<Route path="inbox" component={Inbox}>
  {/* 从 /inbox/messages/:id 跳转到 /messages/:id */}
  <Redirect from="messages/:id" to="/messages/:id" />
</Route>

默认重定向路由


<Route path="/" component={App}>
  <IndexRedirect to="/welcome" />
  <Route path="welcome" component={Welcome} />
  <Route path="about" component={About} />
</Route>

元素跳转

render() {
  return <div>
    <ul role="nav">
      <li><Link to="/about">About</Link></li>
      <li><Link to="/repos">Repos</Link></li>
    </ul>
  </div>
}

hash

browserHistory
hashHistory
createMemoryHistory

import { hashHistory } from 'react-router'

render(
  <Router history={hashHistory} routes={routes} />,
  document.getElementById('app')
)

js跳转

browserHistory.push(path)
this.context.router.push(path)

路由触发器

// 进入触发
<Route path="inbox" component={Inbox}>
  <Route
    path="messages/:id"
    onEnter={
      ({params}, replace) => replace(`/messages/${params.id}`)
    } 
  />
</Route>

// 高级触发器
const requireAuth = (nextState, replace) => {
    if (!auth.isAdmin()) {
        replace({ pathname: '/' })
    }
}
export const AdminRoutes = () => {
  return (
     <Route path="/admin" component={Admin} onEnter={requireAuth} />
  )
}


// 离开时触发


const Home = withRouter(
  React.createClass({
    componentDidMount() {
      this.props.router.setRouteLeaveHook(
        this.props.route, 
        this.routerWillLeave
      )
    },

    routerWillLeave(nextLocation) {
      // 返回 false 会继续停留当前页面,
      // 否则,返回一个字符串,会显示给用户,让其自己决定
      if (!this.state.isSaved)
        return '确认要离开?';
    },
  })
)

     <Route path="/admin" component={Admin} onLeave={requireAuth} />

React-Redux

状态管理机

// Store 保存数据
import { createStore } from 'redux';
// 创建一个store
const store = createStore(fn);

// State 存储数据的容器
const state = store.getState();

// Action 动作,传递给 Store 

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

store.dispatch(action)

// Store 收到 Action 触发 Reducer。

const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
});

// Reducer跟Store相互关联自动触发

const store = createStore(reducer);
const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];
const total = actions.reduce(reducer, 0); //3

// Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。


import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);

流程

view -> action -> store -> reducers -> store.state -> view

demo

// counter接收一个value渲染页面
const Counter = ({ value }) => (
  <h1>{value}</h1>
);

const render = () => {
  // value 传入store的state
  ReactDOM.render(
    <Counter value={store.getState()}/>,
    document.getElementById('root')
  );
};
// 构建一个监听器,state变化重新刷页面
store.subscribe(render);
// 调用render()
render();
const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
  <h1>{value}</h1>
  // 两个函数
  <button onClick={onIncrement}>+</button>
  <button onClick={onDecrement}>-</button>
  </div>
);
// reducer
const reducer = (state = 0, action) => {
  switch (action.type) {
    // 更新state
    case 'INCREMENT': return state + 1;
    case 'DECREMENT': return state - 1;
    default: return state;
  }
};
// 根据reducer创建store
const store = createStore(reducer);

const render = () => {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      // 触发action
      onIncrement={() => store.dispatch({type: 'INCREMENT'})}
      onDecrement={() => store.dispatch({type: 'DECREMENT'})}
    />,
    document.getElementById('root')
  );
};

render();
store.subscribe(render);

异步

与React结合

一个组件三部分ui(html)容器(数据)connect(容器和html的链接)

// TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);

// 完整函数
import { connect } from 'react-redux'
// 第一个参数 state to props 状态转换成参数
// 第二个参数 发出action
const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps)(TodoList)

输入属性

// state对象接收
const mapStateToProps = (state) => {
  return {
    //传入state.todos属性 state.type
    //调用函数改变todos
    todos: getVisibleTodos(state.todos, state.type)
  }
}
// todos 是值,type
const getVisibleTodos = (todos, type) => {
  switch (type) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    default:
      throw new Error('Unknown type: ' + type)
  }
}

输出属性

// 接收dispatch和props返回一个对象
const mapDispatchToProps = (dispatch,ownProp) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter
      });
    }
  };
}

传递State

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

demo

//视图层
// React component
class Counter extends Component {
  render() {
    // 传入两个属性
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncreaseClick: PropTypes.func.isRequired
}

// Action
const increaseAction = { type: 'increase' }

// Reducer
function counter(state = { count: 0 }, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return { count: count + 1 }
    default:
      return state
  }
}

// Store
const store = createStore(counter)

// 容器层
// value绑定到store.state.count
function mapStateToProps(state) {
  return {
    value: state.count
  }
}
// 事件绑定到store.dispatch
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// mapStateToProps 返回value ,value绑定到了store.state.count上
// mapDispatchToProps 返回store.dispath(action) 点击按钮,reductor执行action,改变state的状态
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

与路由连用

const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);

umi

另一种路由

安装

yarn global add umi

创建

mkdir myapp && cd myapp
umi g page index
umi g page users

查看目录结构

$ tree
.
└── pages
    ├── index.css
    ├── index.js
    ├── users.css
    └── users.js

启动

umi dev

目录及约定

.
├── dist/                          // 默认的 build 输出目录
├── mock/                          // mock 文件所在目录,基于 express
├── config/
    ├── config.js                  // umi 配置,同 .umirc.js,二选一
└── src/                           // 源码目录,可选
    ├── layouts/index.js           // 全局布局
    ├── pages/                     // 页面目录,里面的文件即路由
        ├── .umi/                  // dev 临时目录,需添加到 .gitignore
        ├── .umi-production/       // build 临时目录,会自动删除
        ├── document.ejs           // HTML 模板
        ├── 404.js                 // 404 页面
        ├── page1.js               // 页面 1,任意命名,导出 react 组件
        ├── page1.test.js          // 用例文件,umi test 会匹配所有 .test.js 和 .e2e.js 结尾的文件
        └── page2.js               // 页面 2,任意命名
    ├── global.css                 // 约定的全局样式文件,自动引入,也可以用 global.less
    ├── global.js                  // 可以在这里加入 polyfill
├── .umirc.js                      // umi 配置,同 config/config.js,二选一
├── .env                           // 环境变量
└── package.json

基础路由

+ pages/
  + users/
    - index.js
    - list.js
  - index.js

[
  { path: '/', component: './pages/index.js' },
  { path: '/users/', component: './pages/users/index.js' },
  { path: '/users/list', component: './pages/users/list.js' },
]

动态路由

+ pages/
  + $post/
    - index.js
    - comments.js
  + users/
    $id.js
  - index.js

[
  { path: '/', component: './pages/index.js' },
  { path: '/users/:id', component: './pages/users/$id.js' },
  { path: '/:post/', component: './pages/$post/index.js' },
  { path: '/:post/comments', component: './pages/$post/comments.js' },
]

可选的动态路由

+ pages/
  + users/
    - $id$.js
  - index.js

[
  { path: '/': component: './pages/index.js' },
  { path: '/users/:id?': component: './pages/users/$id$.js' },
]

嵌套路由

+ pages/
  + users/
    - _layout.js
    - $id.js
    - index.js

[
  { path: '/users': component: './pages/users/_layout.js'
    routes: [
     { path: '/users/', component: './pages/users/index.js' },
     { path: '/users/:id', component: './pages/users/$id.js' },
   ],
  },
]

通过注释扩展路由

+ pages/
  - index.js


/**
 * title: Index Page
 * Routes:
 *   - ./src/routes/a.js
 *   - ./src/routes/b.js
 */

[
  { path: '/', component: './index.js',
    title: 'Index Page',
    Routes: [ './src/routes/a.js', './src/routes/b.js' ],
  },
]

配置式路由

export default {
  routes: [
    { path: '/', component: './a' },
    { path: '/list', component: './b', Routes: ['./routes/PrivateRoute.js'] },
    { path: '/users', component: './users/_layout',
      routes: [
        { path: '/users/detail', component: './users/detail' },
        { path: '/users/:id', component: './users/id' }
      ]
    },
  ],
};

权限路由

[
  { path: '/', component: './pages/index.js' },
  { path: '/list', component: './pages/list.js', Routes: ['./routes/PrivateRoute.js'] },
]

export default (props) => {
  return (
    <div>
      <div>PrivateRoute (routes/PrivateRoute.js)</div>
      { props.children }
    </div>
  );
}

启用 Hash 路由

export default {
  history: 'hash',
}

页面流转

import Link from 'umi/link';
//链接
export default () => (
  <Link to="/list">Go to list page</Link>
);


import router from 'umi/router';
//命令
function goToListPage() {
  router.push('/list');
}

配置


// 配置文件
//umi 允许在 .umirc.js 或 config/config.js (二选一,.umirc.js 优先)中进行配置,支持 ES6 语法。

//.umirc.local.js
//.umirc.local.js 是本地的配置文件,不要提交到 git,所以通常需要配置到 .gitignore。如果存在,会和 .umirc.js 合并后再返回。

//MI_ENV
//可以通过环境变量 UMI_ENV 区分不同环境来指定配置。

// .umirc.js
export default { a: 1, b: 2 };
// .umirc.cloud.js
export default { b: 'cloud', c: 'cloud' };
// .umirc.local.js
export default { c: 'local' };
不指定 UMI_ENV 时,拿到的配置是:
{
  a: 1,
  b: 2,
  c: 'local',
}
指定 UMI_ENV=cloud 时,拿到的配置是:
{
  a: 1,
  b: 'cloud',
  c: 'local',
}

dva

另一种redux

安装

npm install dva-cli -g

创建应用

dva new dva-quickstart

UI

import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd';

const ProductList = ({ onDelete, products }) => {
  const columns = [{
    title: 'Name',
    dataIndex: 'name',
  }, {
    title: 'Actions',
    render: (text, record) => {
      return (
        <Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
          <Button>Delete</Button>
        </Popconfirm>
      );
    },
  }];
  return (
    <Table
      dataSource={products}
      columns={columns}
    />
  );
};

ProductList.propTypes = {
  onDelete: PropTypes.func.isRequired,
  products: PropTypes.array.isRequired,
};

export default ProductList;

Model

export default {
  namespace: 'products',
  state: [],
  reducers: {
    'delete'(state, { payload: id }) {
      return state.filter(item => item.id !== id);
    },
  },
};

connect

import React from 'react';
import { connect } from 'dva';
import ProductList from '../components/ProductList';

const Products = ({ dispatch, products }) => {
  function handleDelete(id) {
    dispatch({
      type: 'products/delete',
      payload: id,
    });
  }
  return (
    <div>
      <h2>List of Products</h2>
      <ProductList onDelete={handleDelete} products={products} />
    </div>
  );
};

// export default Products;
export default connect(({ products }) =>
 ({products},))(Products);

umi + dva

.umirc.js

export default {
  plugins: [
    [
      'umi-plugin-react',
      {
        dva: true,
      },
    ]
  ],
};
src/models/**/*.js 为 global model
src/pages/**/models/**/*.js 为 page model
global model 全量载入,page model 在 production 时按需载入,在 development 时全量载入
page model 为 page js 所在路径下 models/**/*.js 的文件
page model 会向上查找,比如 page js 为 pages/a/b.js,他的 page model 为 pages/a/b/models/**/*.js + pages/a/models/**/*.js,依次类推
约定 model.js 为单文件 model,解决只有一个 model 时不需要建 models 目录的问题,有 model.js 则不去找 models/**/*.js

Q.E.D.


别云剑本星 智多星本剑