React introduction

jiancheng.zhai

Created: 2016-12-02 Fri 15:18

前端

html / css / javascript

  
      
        HTML  
      
      
        

Hello World! I'm HTML

#hello{  
color:blue;  
}

DOM(html DOM)



  
    My title
  
  
    My Link
    

My header

dom-tree.gif

特性

虚拟 dom

  • 高效 diff 算法
  • 避免直接重置 innerHTML
  • 只更新必要的 dom

jsx

var names = ['Alice', 'Emily', 'Kate'];

ReactDOM.render(
  
{ names.map(function (name) { return
Hello, {name}!
}) }
, document.getElementById('example') );

hello.png

组件

// ES5 way
var HelloMessage = React.createClass({
  render: function() {
    return 

Hello {this.props.name}

; } });
  • 必须实现 render 方法
  • 首字母需大写
  • 只能包含一个顶层标签

组件 - state

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      

Hello, world!

It is {this.state.date.toLocaleTimeString()}.

); } } ReactDOM.render( , document.getElementById('root') );

组件 - 生命周期

class Content extends React.Component {

   componentWillMount() {
      console.log('Component WILL MOUNT!')
   }
   componentDidMount() {
      console.log('Component DID MOUNT!')
   }
   componentWillReceiveProps(newProps) {    
      console.log('Component WILL RECIEVE PROPS!')
   }
   shouldComponentUpdate(newProps, newState) {
      return true;
   }
   componentWillUpdate(nextProps, nextState) {
      console.log('Component WILL UPDATE!');
   }
   componentDidUpdate(prevProps, prevState) {
      console.log('Component DID UPDATE!')
   }
   componentWillUnmount() {
      console.log('Component WILL UNMOUNT!')
   }
   render() {
      return (... );
   }
}

html 示例




  
    
    
    
    
    
    
    
  


  

react - native

开发

npm

$ sudo npm install -g npm
$ npm install react
{
  "name": "react-demo",
  "version": "1.0.0",
  "description": "practice with react",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "react"
  ],
  "author": "enigma",
  "license": "MIT",
  "dependencies": {
    "react": "^15.3.0",
    "react-router": "^2.7.0",
    "react-router-redux": "^4.0.5"
    },
  "devDependencies": {
    "redux-devtools": "^3.3.1"
   }
 }

webpack

webpack.jpg

$ webpack --watch
Hash: 3117d6437f7c7b27e341
Version: webpack 1.13.1
Time: 1641ms
        Asset     Size  Chunks             Chunk Names
    bundle.js  2.37 MB       0  [emitted]  main
bundle.js.map  2.83 MB       0  [emitted]  main
    + 800 hidden modules

webpack - 配置

npm install -g babel
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var path = require("path");
module.exports = {
  entry: [
    'whatwg-fetch',
    "./app/index.js"
  ],
  devtool: 'source-map',
  output: {
    path: __dirname + '/static',
    filename: "bundle.js"
  },
  resolve: {
      extensions: ['', '.scss', '.css', '.js', '.json'],
      modulesDirectories: [
          'node_modules',
          path.resolve(__dirname, './node_modules')
      ]
  },
  module: {
    loaders: [
      {
        test: /\.js?$/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-0'],
          plugins: ['transform-runtime', 'transform-decorators-legacy']
        },
        exclude: /node_modules/
      },
      { 
        test: /(\.scss|\.css)$/,
        loaders:["style", "css", "sass"]
      }
    ]
  },
  plugins: [
      new ExtractTextPlugin('theme.css', { allChunks: true }),
  ]
};

devtools

module.exports = {
  devtool: 'source-map',
}
const DevTools = createDevTools(
    
    
    
)
const store = createStore(rootReducer, DevTools.instrument())
ReactDOM.render(
    
    
, document.getElementById('demo1'));

ES6+ - 类

// The ES5 way
var Photo = React.createClass({
  handleDoubleTap: function(e) { … },
  render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
  handleDoubleTap(e) { … }
  render() { … }
}

ES6+ - 属性初始化

// The ES5 way
var Video = React.createClass({
  getDefaultProps: function() {
  return {
      autoPlay: false,
      maxLoops: 10,
    };
  },
getInitialState: function() {
  return {
      loopsRemaining: this.props.maxLoops,
    };
  },
  propTypes: {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  },
});
// The ES6+ way
class Video extends React.Component {
  static defaultProps = {
    autoPlay: false,
    maxLoops: 10,
  }
  static propTypes = {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  }
  state = {
    loopsRemaining: this.props.maxLoops,
  }
}

ES6+ - 箭头函数

// Manually bind, wherever you need to
class PostInfo extends React.Component {
  constructor(props) {
  super(props);
    // Manually bind this method to the component instance...
    this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
  }
  handleOptionsButtonClick(e) {
    // ...to ensure that 'this' refers to the component instance here.
    this.setState({showOptionsModal: true});
  }
}

class PostInfo extends React.Component {
    handleOptionsButtonClick = (e) => {
    this.setState({showOptionsModal: true});
  }
}

// ES5
var selected = allJobs.filter(function (job) {
    return job.isSelected();
});
// ES6
let selected = allJobs.filter(job => job.isSelected());

ES6+ - 字符串模板

//ES5 way
var Form = React.createClass({
  onChange: function(inputName, e) {
    var stateToSet = {};
    stateToSet[inputName + 'Value'] = e.target.value;
    this.setState(stateToSet);
  },
});

//ES6 way
class Form extends React.Component {
  onChange(inputName, e) {
  this.setState({
    [`${inputName}Value`]: e.target.value,
  });
  }
}

ES6+ - 扩展运算符

class AutoloadingPostsGrid extends React.Component {
  render() {
    var {
      className,
      ...others, // contains all properties of this.props except for className
    } = this.props;
  return (
    
); } }

babel - .babelrc

npm install -g babel
{
  "presets": ["stage-0", ["es2015", { "loose": true }]],
  "env": {
    "test": {
      "plugins": ["istanbul"]
    }
  }
}

语法检查 - eslint

npm install eslint babel-eslint
/ Use this file as a starting point for your project's .eslintrc.
// Copy this file, and add rule overrides as needed.
{
  "extends": "airbnb"
}

fetch / promise

module.exports = {
  entry: [
    'whatwg-fetch',
  ],
}
import 'whatwg-fetch';
fetch('/token',
      {method: "POST",
       headers:{
         'Accept': 'application/json',
         'Content-Type': 'application/json'},
       body: JSON.stringify({sentences: getSelectedSentences(selectedSentences)})
      })
  .then(response => response.json())
  .then(json => handleToken(json))
  .catch(function(e){console.log('parsing failed', e)});

ES6 support

require('babel-core/register');
require('./server');

flux

flux.png

redux

npm install react-redux

redux.jpg

redux - store

import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
  • 全局唯一的数据容器
  • 一个 state 对应唯一 view

redux - action

// 普通 action
const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};
// action creator
const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
const action = addTodo('Learn Redux');

redux - dispatch

import { createStore } from 'redux';
const store = createStore(fn);
// 普通 action 版本
store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
// action creator 版本
store.dispatch(addTodo('Learn Redux'));

redux - reducer

export default function (state = [], action) {
  switch(action.type){
  case "UPDATE_SEARCH_TEXT":
    return {...state, searchText: action.data}

  case "SEARCH_QUERY":
    return {...state, searchRes: action.data}

  case "UPDATE_MULTISELECT_OPTIONS":
    return {...state, classOptions: action.data}

  case "UPDATE_CLASS_SELECTION":
    return {...state, classSelection: action.data}
  default:
    return state

  }
}
import { combineReducers } from 'redux';

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName
})
export default todoApp;

redux - connect

import { connect } from 'react-redux'
const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (
  dispatch,
  ownProps
) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter
      });
    }
  };
}
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
  • connect 从 UI 组件生成容器

redux - provider

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

let store = createStore(todoApp);

render(
  
    
  ,
  document.getElementById('root')
)

react-router

// route.js
const routes = (
    
    
    
    
    
    )

export default routes
// index.js
import { browserHistory, Router, Route } from 'react-router'
const history = syncHistoryWithStore(browserHistory, store)

ReactDOM.render(
    
    
, document.getElementById('demo1'));

redux-thunk

npm install redux-thunk
import { compose, createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
var buildStore = compose(applyMiddleware(thunk))(createStore);

react-grid-layout

npm install react-grid-layout
import {Responsive, WidthProvider} from 'react-grid-layout';
const ResponsiveReactGridLayout = WidthProvider(Responsive);

class SearchGridLayout extends Component {
  constructor(props, context){
    super(props, context);
  }
  render() {
    let layouts = {
      lg:[{i:"searchText", x: 3, y: 2, w: 5, h: 0.2, static:true},
          {i:"searchBtn", x: 8, y: 2, w: 1, h: 0.2, static:true},
          {i:"categorySelection", x: 3, y: 1, w: 3, h: 0.2, static:true},]
    }
    return(
        
        {this.props.children}
      
    )
  }
}

material-ui

npm install material-ui
class Search extends Component {
  constructor(props, context){
    super(props, context)
  }
  search(query){ }
  render() {
    return (
        
        
         this.search(this.props.query)} />
        
    )
}

searchbar.png

后端

server side render

renderToString
renderToStaticMarkup
import path from 'path'
import Express from 'express'
import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import counterApp from './reducers'
import App from './containers/App'

const app = Express()
const port = 3000

// This is fired every time the server side receives a request
app.use(handleRender)

// We are going to fill these out in the sections to follow
function handleRender(req, res) { /* ... */ }
function renderFullPage(html, preloadedState) { /* ... */ }
app.listen(port)

server side render

import { renderToString } from 'react-dom/server'

function handleRender(req, res) {
  // Create a new Redux store instance
  const store = createStore(counterApp)

  // Render the component to a string
  const html = renderToString(
    
      
    
  )

  // Grab the initial state from our Redux store
  const preloadedState = store.getState()

  // Send the rendered page back to the client
  res.send(renderFullPage(html, preloadedState))
}

server side render

function renderFullPage(html, preloadedState) {
  return `
    
    
      
        Redux Universal Example
      
      
        
${html}
` }

restful api - Http 动词

  • GET(SELECT):从服务器取出资源(一项或多项)
  • POST(CREATE):在服务器新建一个资源
  • PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)
  • PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)
  • DELETE(DELETE):从服务器删除资源

restful api - 序列化 & 反序列化

  • 格式:json , xml
{
  "page": 1,            # 当前是第几页
  "pages": 3,          # 总共多少页
  "per_page": 10,      # 每页多少数据
  "has_next": true,    # 是否有下一页数据
  "has_prev": false,    # 是否有前一页数据
  "total": 27          # 总共多少数据
}

restful api - 校验

  • 数据类型校验,如字段类型如果是 int,那么给字段赋字符串的值则报错
  • 数据格式校验,如邮箱或密码,其赋值必须满足相应的正则表达式,才是正确的输入数据
  • 数据逻辑校验,如数据包含出生日期和年龄两个字段,如果这两个字段的数据不一致,则数据校验失败

restful api - 包含版本信息

# V1.0
/api/v1/posts/
/api/v1/drafts/

# V2.0
/api/v2/posts/
/api/v2/drafts/

restful api - url 使用名词

  • 指向资源而非行为(使用名词)
# Bad APIs
/api/getArticle/1/
/api/updateArticle/1/
/api/deleteArticle/1/

# Good APIs
/api/Article/1/

restful api - tips

  • url 区分大小写
# different url
/posts
/Posts
  • 反斜线/结尾, 需重定向
# different url
/posts/
/posts
  • 连词符
# Good
/api/featured-post/

# Bad
/api/featured_post/

Q & A