React實戰一

1. 搭建環境

npm install -g creat-react-app
# 建立一個項目
create-react-app jianshu
cd jianshu
npm run start
# 安裝styled-components 全局統一管理css
npm install --save styled-components
# 全局樣式引入
https://meyerweb.com/eric/tools/css/reset/
background-size: contain;

2. React知識點

1. 組件

組件:就是將整個UI拆分爲不少小的,可重用的UI,每個小的UI獨立作本身的事情。html

1.1 定義一個組件

一個組件須要繼承React.Component 重寫render()函數,返回JSXreact

注意點:ios

1.只要有JSX的地方必須引入Reactgit

2.返回值中只能有一個元素包裹,div包裹整個JSX,以下是錯誤的返回方式es6

return (
 <div>
   hello world
 </div>
 <div>
   hello world
 </div>
);
import React, { Component } from 'react';
class App extends Component {
  render() {
    return (
      <div>
        hello world
      </div>
    );
  }
}
export default App;

1.2 組合與拆分組件

引用官方的一句話:Don’t be afraid to split components into smaller components.github

儘量的讓每個組件都能分工明確,減小重複,加大可重用。例如表格組件,就必須被拆分爲一個單獨的組件,它可能在各個地方被用到。web

組件拆分優勢:讓開發顯得更加清晰明瞭。ajax

未拆分組件看起來就沒有食慾算法

return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
 );

1.案例解析

將首頁拆分爲Header, Right,以下定義一個首頁組件,在首頁組件中分別引入Header和Right組件,這就是一個簡單的組件拆分和組合.

<React.Fragment />是一個虛擬的組件,僅僅是爲了包裹其中的元素,並不會出如今頁面上

Index.js

import React from 'react'
import Header from './Header'
import Right from './Right'
class Index extends React.Component {
  render() {
    return (
      <React.Fragment>
        <Header />
        <Right />
      </React.Fragment>
    );
  }
}
export default Index

Header.js

import React from 'react'
class Header extends React.Component {
  render() {
    return (
      <React.Fragment>
        <div>i am header component</div>
      </React.Fragment>
    );
  }
}
export default Header

Right.js

import React from 'react'
class Right extends React.Component {
  render() {
    return (
      <React.Fragment>
        <div>i am right component</div>
      </React.Fragment>
    );
  }
}
export default Right

1.3 組件傳值

組件傳值分爲如下幾種狀況:1. 父組件傳值到子組件;2.子組件向父組件傳值;3.隔代組件傳值

1. 父傳子

注意點: props是隻讀的

例如Index.jsHeader.js組件傳值

{/* Index.js 傳遞title屬性 */}
    return (
      <React.Fragment>
        <Header title="Header Title"/>
        <Right />
      </React.Fragment>
    );

    {/*Header.js 接受值*/}
    return (
      <React.Fragment>
        <div>{this.props.title}</div>
      </React.Fragment>
    );

1.4 state

state能夠用來存儲數據,記性狀態管理。

it is private and fully controlled by the component.

render函數執行:組件state和props發生改變,render函數會被執行,當父組件從新渲染時候,子組件render函數也會被從新執行

class Index extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      title: 'Header title'
    }
  }

  render() {
    return (
      <React.Fragment>
        <Header title={this.state.title}/>
        <Right />
      </React.Fragment>
    );
  }
}
export default Index

1.5 PropTypes

https://reactjs.org/docs/typechecking-with-proptypes.html

當組件之間傳值的時候,接收一方每每須要對參數進行校驗,這就是PropTypes的做用

import PropTypes from 'prop-types'

class ReactUI extends React.Component {
  render() {
    return (
        
    )
  }
}
//屬性類型
ReactUI.propTypes = {
  //表示name必須是string類型
  name: PropTypes.string
  //屬性必傳
  id: PropTypes.element.isRequired
}

默認值

The propTypes typechecking happens after defaultProps are resolved

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

1.5 生命週期函數

在頁面加載的過程當中,特定時間點自動執行的函數稱爲生命週期函數。

生命週期的四個階段:

  1. Initialization

這一階段主要加載props和state

  1. Mounting:頁面第一次加載的時候纔會執行的掛載

componentWillMount:在組件即將被掛載到頁面的時刻執行

render:

componentDisMount:在組件掛載完成以後會被執行

​ 使用場景:用於發送ajax請求

  1. Updation:數據發生變化的時候會被執行
  • props:

componentWillReceiveProps:

​ 子組件從父組件接受屬性,第一次存在父組件,render函數執行,該函數不會被執行,當父組件render函數從新被執行,就會執行這個函數

shouldComponentUpdate:組件是否須要被更新,返回boolean值

​ 使用場景:shouldComponentUpdate(nextProps, nextState);這兩個參數來接受即將改變的props和state的值;演示:

shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.title !== this.props.title) { //放生改變,則須要從新渲染
      return true
    } else {
      return false
    }
  }

componentWillUpdate: 當組件被更新以前被執行,可是取決於shouldComponentUpdate的返回結果,

render

componentDidUpdate:組件更新完成以後會被執行

  • states:

shouldComponentUpdate

componentWillUpdate

componentDidUpdate:

  1. Unmounting

componentWillUnmount: 組件從頁面移除的時候會被執行

1.6 無狀態組件

無狀態組件: 就是組件中只含有render()函數的組件,

import React from 'react'

class Header extends React.Component {
  render() {
    return (
      <React.Fragment>
        <div>xx {this.props.title}</div>
      </React.Fragment>
    );
  }
}
export default Header

這種組件能夠被簡化爲無狀態組件

export default (props, context) => {
  return (
    <React.Fragment>
      <div>xx {props.title}</div>
    </React.Fragment>
  );
}

1.7 List and Key

案例:使用List集合渲染一個頁面

key的做用:

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity

這個主要和虛擬DOM有關,能夠提升虛擬DOM性能。

兄弟姐妹之間的key必須獨一無二

import React from 'react'

class Event extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      data: [1, 2, 3, 4, 5, 6, 7]
    }

    this.handClick = this.handClick.bind(this);
  }
  render() {
    return (
     <div>
       <ul>
         {
           this.state.data.map((item) => (
            <li key={item}>{item}</li>
           ))
         }
       </ul>
     </div>
    )
  }
}
export default Event

2. JSX

JSX 既不是string也不是html,是React獨有的一種語法,jsx中須要注意的點:

  1. 標籤的類class 如:<div class='show'></div> 會和ES6關鍵字衝突會使用className代替
  2. <label></label> for屬性也會使用htmlFor 代替

3. 虛擬DOM

虛擬DOM就是一個JS對象,用來描述真實DOM

jsx轉換成js對象

<div className='show'>hello world</div>
React.createElement('div',{className: 'show'}, 'hello world');

流程講解

  1. state數據
  2. jsx模板
  3. 數據 + 模板生成虛擬DOM
  4. 使用虛擬DOM來來生成真實DOM, 顯示在頁面上
  5. state發生改變
  6. 數據 + 模板生成新的虛擬DOM
  7. 比較原始的虛擬DOM和新的虛擬DOM之間的區別,找到不一樣之處
  8. 操做DOM,改變不一樣的地方

diff算法

上面的第七步驟是如何比對不一樣之處呢,這裏使用了diff算法。

同層比對:第一層有差別,則不會再進行比對,直接向下所有渲染,若是第一層相同,則向下繼續比較。

根據Key作關聯來進行比對: 同層的key必須固定不變,這樣才能再進行比對的時候準確的找到原始的dom節點。假如使用index做爲key值,當刪除一個元素,那個被刪除元素後面的全部標籤key值都會被改變,那麼進行比對的時候,就會出現性能消耗。

5. 函數綁定

React events are named using camelCase, rather than lowercase.

例如onClick, onBlur...

import React from 'react'
import './event.css'

class Event extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      isShow: true
    }
  }
  render() {
    return (
     <div>
       <button onClick={this.handClick.bind(this)}>切換</button>
       <div className={this.state.isShow ? 'show':'hidden'}>你好</div>
     </div>
    )
  }

  handClick() {
    this.setState((state) => ({
      isShow: !state.isShow
    }))
  }
}
export default Event

傳遞參數

render() {
    return (
     <div>
       <button onClick={() => this.handClick(this.state.isShow)}>切換</button>
       <div className={this.state.isShow ? 'show':'hidden'}>你好</div>
     </div>
    )
  }

  handClick(isShow) {
    this.setState((state) => ({
      isShow: !isShow
    }))
  }

傳遞參數的幾種方式

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
<SearchSwitch onClick={() => switchItem(page, totalPage)}>換一批</SearchSwitch>

3. Redux

https://redux.js.org/introduction/getting-started

# 安裝
npm install --save redux

1. 瞭解是三個概念

Actions: 就像一個快遞,裏面包含地址(行爲),以及物品(數據)

Reducers:就像一個快遞員,負責分發不一樣的快遞,根據地址(行爲)派送到不一樣地方

Store: 就像一個總站,能夠存儲這些快遞,最後返回給用戶。

與物流之間的區別就是:store不可變,只能夠返回數據,而原先的數據一直保留在store裏面

1.1 演示

第一步:建立Store index.js

這裏藉助redux createStore方法建立store,而且引入reducer

import { createStore } from 'redux'
import reducer from './Reducers'

const store = createStore(
  reducer,
  // 這個可使用Chrome redux 插件進行調試
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store

第二步:建立一個reducer

reducer 裏面保存數據的初始狀態,defaultState

解析action的行爲

const defaultState = {
  list: ['hello', 'world']
}

const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'add_list_action':
      return {list: action.data}
    default:
      return state
  }
}
export default reducer

第三步:使用redux

  1. this.state = store.getState() 從store中獲取數據
  2. store.subscribe(this.handSubscribe)訂閱數據,監聽數據的變化
  3. handClick()方法中主要定義action,而後利用store進行分發
import React from 'react'
import store from './store'

class ReduxUI extends React.Component {

  constructor(props) {
    super(props)
    this.state = store.getState()
    this.handClick = this.handClick.bind(this)
    this.handSubscribe = this.handSubscribe.bind(this)
    store.subscribe(this.handSubscribe)
  }

  render() {
    return (
      <div>
        <button onClick={this.handClick}>點擊我</button>
        <ul>
          {
            this.state.list.map((item) => {
              return <li key={item}>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
  handClick() {
    //建立一個Action
    const addListAction = () => ({
      type: 'add_list_action',
      data: ['1', '2', '3']
    })
    //這是一個異步操做
    store.dispatch(addListAction())
  }

  handSubscribe() {
    this.setState(store.getState())
  }
}
export default ReduxUI

2. react-redux中間件

改造1>1.1 中的代碼

nmp install --save react-redux

新增長一個父組件,統一管理store,

這個組件擁有store,<Provider store={store}>表示改下面的全部元素均可以使用store中的數據

import React, { Component } from 'react';
import store from './pages/redux/store'
import { Provider } from 'react-redux'
import ReduxUI from './pages/redux/ReduxUI'
class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <ReduxUI/>
      </Provider>
    );
  }
}

export default App;

步驟三中的修改

第一步:添加connect 方法

第三步:映射state中的屬性,定義分發的方法

第四步:鏈接組件

import React from 'react'
import {connect } from 'react-redux'

class ReduxUI extends React.Component {

  render() {
    //從react-redux中接收store中的信息,以及分發action的方法
    const { list, handClick } = this.props
    return (
      <div>
        <div>
          <button onClick={handClick}>點擊我</button>
          <ul>
            {
              list.map((item) => {
                return <li key={item}>{item}</li>
              })
            }
          </ul>
        </div>
      </div>
    )
  }

}
//state就是store中state
const mapStateToProps = (state) => ({
  list: state.list
})
//這裏定義分發action的方法,dispatch 就是store的dispatch方法
const mapDispatchProps = (dispatch) => ({
  handClick() {
    //建立一個Action
    const addListAction = () => ({
      type: 'add_list_action',
      data: ['1', '2', '3']
    })
    dispatch(addListAction())
  }
})
//鏈接組件,而且把屬性和action行爲傳遞給組件
export default connect(mapStateToProps, mapDispatchProps)(ReduxUI)

4. 其餘

1. Redux-thunk

Redux Thunk middleware allows you to write action creators that return a function instead of an action

npm install redux-thunk

to enable Redux Thunk, useapplyMiddleware()

這裏配合redux-devtools-extension調試工具一塊兒使用

import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk  from 'redux-thunk'
// 這個就是讓調試插件配合中間件使用
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

const store = createStore(
  reducer,
  composeEnhancers(applyMiddleware(thunk))
)
export default store
// 定義一個方法,處理鼠標聚焦行爲
//而後dispatch分發action
handFocus(list) {
    (list.size === 0) && dispatch(actionCreator.getSearchItems())
    dispatch(actionCreator.inputFocusAction())
}

//另一個文件
//===================================
// 這裏就是上面的action,這個action是一個方法,在該方法裏面發送ajax異步請求,
//異步請求以後繼續分發另一個action
export const getSearchItems = () => {
  return (dispatch) => {
    axios.get("/api/item.json").then((res) => {
      dispatch(searchItemsAction(res.data.data))
    })
  }
}

正如官方文檔所說:redux-thunk容許你分發一個方法類型的action

2. Redux-saga

asynchronous things like data fetching and impure things like accessing the browser cache

npm install --save redux-saga
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './Reducers'
import createSagaMiddleware from 'redux-saga'
import reduxSaga from './reduxSaga'
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
  reducer,
  composeEnhancers(applyMiddleware(sagaMiddleware))
)
//運行本身的reduxSaga
sagaMiddleware.run(reduxSaga)

export default store

reduxSaga.js

import { takeEvery, put } from 'redux-saga/effects'
import axios from 'axios'

function* getJson() {
  const res = yield axios.get('./data.json')
  yield put({type:"add_list_action", data: res.data}) //繼續派發action
}

function* reduxSaga() {
  // 當store分發'add_list_action' 這個action的時候,會調用getJson方法
  yield takeEvery("add_list__pre_action", getJson)
}
export default reduxSaga

5. immutable

immutable能夠保證數據一旦建立,就不會被改變

npm install --save immutable
//目的:將store變成不可變,這是一個簡單reducer.js
import * as constants from './constant' //es6語法
import { fromJS } from 'immutable'
//這裏將defaultStore變成一個immutable對象
const defaultStore = fromJS({
  focused: false,
  mouseIn: false,
  list: [],
  page: 1,
  totalPage: 1
})

export default (state = defaultStore, action) => {
  switch (action.type) {
    case constants.input_focused:
      //這裏的set  immutable對象的一個值,其實並非改變了state,而是返回一個新的對象
      //經過set能夠簡化咱們的操做,不用從新構建所有的對象
      return state.set('focused', true)
    default:
      return state
  }
}


// 如何獲取一個immutable對象呢?
const mapStateToProps = (state) => ({
  //這裏表示獲取header組件下的一個immutable對象
   //這裏是 redux-immutable 獲取數據的形式
    // state.header.get('focused') 爲immutable獲取數據的格式
  focused: state.getIn(['header', 'focused']), //state.get('header').get('focused')
  mouseIn: state.getIn(['header', 'mouseIn']),
  list: state.getIn(['header', 'list']),
  page: state.getIn(['header', 'page']),
  totalPage: state.getIn(['header', 'totalPage'])
})

//經過action傳遞參數的時候也應該是一個immutable對象
export const searchItemsAction = (data) => ({
  type: constants.render_list,
  data: fromJS(data),
  totalPage: Math.ceil(data.length / 10)
})

5.1 redux-immutable

redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.jsstate.

咱們常常面臨不少不少組件,可是若是把全部組件的store,reducer, action 維護在一個目錄下,那將是慘目忍睹的,因此一般狀況下咱們會分開管理redux,而後將全部的reducer組合在一塊兒

npm install --save redux-immutable
//import { combineReducers } from 'redux'
import { combineReducers } from 'redux-immutable' //能夠管理immutale對象
import { reducer as headerReducer} from '../header/store'

const reducer = combineReducers({
  header: headerReducer
})
export default reducer

5. react-router-dom

React Router is a collection of navigational components that compose declaratively with your application.

npm install --save react-router-dom
import { BrowserRouter, Route } from "react-router-dom"
...
<BrowserRouter>
    {/* 表示路由到一個組件 */}
    <Route path="/home" exact component={Home} />
</BrowserRouter>

Route: 表示要路由的到哪個組件

Link: 表示一個鏈接,跳轉到某個頁面

6. react-loadable

一個項目,不一樣的頁面應該按需加載,而不是當首頁出來以後加載全部的組件,例如咱們訪問首頁的時候只會加載首頁相關的資源,訪問詳情頁的時候加載詳情頁的代碼,這樣能夠提升首頁訪問速度以及用戶體驗

npm install --save react-loadable

案例演示

import React from 'react'
import Loadable from 'react-loadable'
const LoadableComponent = Loadable({
  //這裏`reactUI`是一個組件,表示這個組件將會被按需加載
  loader: () => import('./reactUI'),
  // 這裏是一個函數,能夠是一個組件,表示頁面加載前的狀態
  loading: () => <div>正在加載</div>
})
//返回無狀態組件
export default () => {
  return <LoadableComponent/>
}

7. 在React 使用各類CSS

1. styled-components

npm install --save styled-components

定義全局樣式

import { createGlobalStyle } from 'styled-components'
export const ResetCSS = createGlobalStyle`
    body {
    line-height: 1;
  }
    ....
`
// 引用全局樣式
import { ResetCSS } from './style'
...
render() {
  return (
    <ResetCSS />
  )
}
相關文章
相關標籤/搜索