react+redux+router入門(mén)總結(jié)

react+redux+router入門(mén)總結(jié)

目錄

  1. 構(gòu)建配置
  2. React組件舰涌、css module
  3. React Router 使用
  4. Redux Redux 使用
  5. 注意問(wèn)題
  6. 資料整理

一、構(gòu)建配置

1)使用 react-app-rewired 對(duì) create-react-app 的默認(rèn)配置進(jìn)行自定義

1你稚、 引入 react-app-rewired 并修改 package.json 里的啟動(dòng)配置瓷耙。由于新的 react-app-rewired@2.x 版本的關(guān)系,你還需要安裝 customize-cra

yarn add react-app-rewired customize-cra babel-plugin-import less less-loader --dev
/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
}

2刁赖、根目錄創(chuàng)建 config-overrides.js

const path = require('path');
const { override, fixBabelImports, addLessLoader, addWebpackAlias } = require('customize-cra');

const addWebpackConfig = () => (config) => {
  if (process.env.NODE_ENV === 'production') {
    config.devtool = false
    config.output.publicPath = '//static.kuaizi.co/super-recommend/'
  }

  return config
}

module.exports = override(
  // 按需加載
  fixBabelImports('lodash', {
    libraryDirectory: '',
    camel2DashComponentName: false
  }),
  // 按需加載
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: true
  }),
  addLessLoader({
    noIeCompat: true,
    javascriptEnabled: true,
    localIdentName: '[local]--[hash:base64:5]', // 開(kāi)啟less module
    modifyVars: { // less 變量
      '@primary-color': '#0999aa',
      '@success-color': '#45A767',
      '@layout-header-background': '#0999aa'
    }
  }),
  addWebpackAlias({
    "@": path.resolve(__dirname, "src")
  }),
  addWebpackConfig()
)

2)PUBLIC_URL

1搁痛、添加環(huán)境變量到build, dev訪問(wèn)需要配合proxy

/* package.json */
"scripts": {
  "build": "PUBLIC_URL=//xxx.cn react-app-rewired build",
}

2、使用

index.html

<script src="%PUBLIC_URL%/common/js/utils.js"></script>

JavaScript

render() {
    return <img src={`${process.env.PUBLIC_URL}/common/img/logo.png`} />;
}

3)proxy

1 安裝http-proxy-middleware依賴

yarn add http-proxy-middleware --dev

2 在src目錄下新建 setupProxy.js

const proxy = require("http-proxy-middleware");

module.exports = function(app) {
  app.use(
    // api代理
    proxy("/api", {
      target: "http://xxx.cn",
      secure: false,
      changeOrigin: true,
      pathRewrite: {
         "^/api": ""
      }
    }),

    // cnd資源代理
    proxy("/common", {
      target: "http://xxx.cn",
      secure: false,
      changeOrigin: true
    })
  );
};

二宇弛、React組件

1)有狀態(tài)組件 class component

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

2)無(wú)狀態(tài)函數(shù)組件

import PropTypes from 'prop-types'
const Example = props => {
  const { count, onClick } = props;
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={onClick}></button>
    </div>
  )
}

Example.propTypes = {
  count: PropTypes.number,
  onClick: PropTypes.func,
}
Example.defaultProps = {
  count: 0,
  onClick: (() => {})
}

3) HOC高階組件

HOC(High Order Component) 是 react 中對(duì)組件邏輯復(fù)用部分進(jìn)行抽離的高級(jí)技術(shù)鸡典,但HOC并不是一個(gè) React API 。 它只是一種設(shè)計(jì)模式枪芒,類(lèi)似于裝飾器模式彻况。

在 Vue 中通常我們采用: mixins

const DataStorageHoc = WrappedComponent => {
  class DataStorage extends React.Component{
    state = {
      data: null
    }
    
    componentWillMount() {
      const data = localStorage.getItem('data')
      this.setState({ data })
    }

    render() {
      const { forwardedRef, ...rest} = this.props
      // 2. 我們接收到 props 中被改名的 forwardedRef 然后綁定到 ref 上
      return <WrappedComponent ref={forwardedRef} data={this.state.data} {...rest} /> 
    }
  }

  return React.forwardRef((props,ref)=>{
     // 1. 我們接收到 ref 然后給他改名成 forwardedRef 傳入到props中, 因?yàn)榇藃ef是保留字段谁尸,需要dom元素才能接收
    return <DataStorage {...props} forwardedRef={ref} ></DataStorage>
  })
}

使用

// example.jsx
import DataStorageHoc from './hoc/data-storage.jsx'

// wrapped component
class Example extends React.Component{
  echo = () => {
    console.log('hello')
  }
  render () {
    return <h2>{this.props.data}</h2>
  }
}
export default DataStorageHoc(Example)

// ================================

// 裝飾器(decorator)模式
@DataStorageHoc
class Example extends React.Component{
  echo = () => {
    console.log('hello')
  }
  render () {
    return <h2>{this.props.data}</h2>
  }
}
export default Example

// ================================

// 調(diào)用
// app.jsx
class App extends React.Component {
  constructor(props){
    super(props)
    this.exampleRef = React.createRef()
  }

  handleEcho = () => {
    this.exampleRef.current.echo()
  }

  render() {
    return (
      <div>
        <Example ref={this.exampleRef}></Example>
        <button onClick={this.handleEcho}>echo</button>
      </div>
    )
  }
}

export default App

4) hooks是有狀態(tài)的函數(shù)

hook作用: <b>將可復(fù)用的最小單元從組件層面進(jìn)一步細(xì)化到邏輯層面。狀態(tài)邏輯和UI是解耦的纽甘。<b>

import { useState } from 'react'
const Example = () => {
    const [count, setCount] = useState(0);
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
      </div>
    );
}

5) 自定義hooks

function useCount(initialValue = 0) {
  const [count, setCount] = useState(initialValue)
  useEffect(() => {
    service.getInitialCount().then(data => {
      setCount(data)
    })
    return () => {
     console.log('計(jì)數(shù)完成')
    }
  }, [])
  function addCount() {
    setCount(c => c + 1)
  }
  return { count, addCount }
}

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth)

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth)

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  })

  return width
}

const App = () => {
  const { count, addCount } = useCount(0)
  const width = useWindowWidth()

  const onChange = (e) => {
    handleNameChange(e.target.value)
  }
  
  return (
    <div>
      <p>window width {width}</p>
      <p>You clicked {count} times</p>
      <button onClick={addCount}>Click me</button>
    </div>
  )
}

6) css動(dòng)態(tài)樣式功能

classnames

1良蛮、安裝

yarn add classnames

2、使用方法

import classnames from 'classnames'
 
<div className=classnames({
    'class1': true,
    'class2': true
    )>
</div>

// 其他類(lèi)型
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

7) less module

  • 文件名規(guī)則 xxx.module.less悍赢, 必須以.module.less結(jié)尾
  • 命名:可以使用駝峰或者連接線命名

index.module.less

.list {
  height: 100%;
  overflow-y: auto;
}
.listItem {
  height: 50px;
}

.itemActive {
  color: aqua;
}

.item-disabled {
  color: #999;
}

使用樣式

import classNames from 'classnames'
import styles from './index.module.less'

const Example = props => {
  return (
    <ul class={styles.listScroll)}>
      <li className={classNames('item', styles.listItem, {styles.itemActive : true})}>...</li>

      <li className={classNames('item', styles.listItem, {styles['item-disabled'] : true})}>...</li>
    </ul>
  )
}

三决瞳、React Router

1) 安裝

創(chuàng)建 Web應(yīng)用,使用

yarn add react-router-dom

創(chuàng)建 navtive 應(yīng)用左权,使用

yarn add react-router-native

2) 路由模式

BrowserRouter模式 創(chuàng)建的 URL 形式如下:

http://example.com/some/path

HashRouter模式 創(chuàng)建的 URL 形式如下:

http://example.com/#/some/path

3) Route組件

<Route>組件是react router v4里最有用的組件皮胡。無(wú)論何時(shí)你需要在匹配某個(gè)路徑的時(shí)候繪制一個(gè)組件,那么就可以使用Route組件涮总。

Route組件可以使用如下的屬性:

  • path屬性胸囱,字符串類(lèi)型祷舀,它的值就是用來(lái)匹配url的瀑梗。
  • component屬性,它的值是一個(gè)組件裳扯。在path匹配成功之后會(huì)繪制這個(gè)組件抛丽。
  • exact屬性,這個(gè)屬性用來(lái)指明這個(gè)路由是不是排他的匹配 (絕對(duì)匹配)饰豺。
  • strict屬性亿鲜, 這個(gè)屬性指明路徑只匹配以斜線結(jié)尾的路徑。

還有其他的一些屬性冤吨,可以用來(lái)代替component屬性

  • render屬性蒿柳,一個(gè)返回React組件的方法,只要你的路由匹配了漩蟆,這個(gè)函數(shù)才會(huì)執(zhí)行
  • children屬性垒探,返回一個(gè)React組件的方法。只不過(guò)這個(gè)總是會(huì)繪制怠李,即使沒(méi)有匹配的路徑的時(shí)候圾叼。

使用component

<Route exact path="/" component={HomePage} />

使用render

<Route path="/" render={(props) => (
    <HomePage {...props} />
)} />

使用children


<Route path="/" children={(props) => (
    <div>children</div>
)} />

exact

http://example.com/#/path
<Route exact path="/" component={HomePage} />  // 不匹配

非exact

http://example.com/#/path
<Route  path="/" component={HomePage} />  // 匹配

4)Switch組件

只有第一個(gè)匹配的路由<Route>或者<Redirect>會(huì)被繪制,匹配到一個(gè)就不再匹配

import { Switch, Route } from 'react-router-dom'
<Switch>
  <Route exact path="/" component={HomePage} />
  <Route path="/about" component={AboutPage} />
  <Route component={NotFound} />
</Switch>

5)Link捺癞、NavLink夷蚊、Redirect組件

Link組件需要用到to屬性,這個(gè)屬性的值就是react router要跳轉(zhuǎn)到的地址

NavLink是Link的一個(gè)子類(lèi)髓介,在Link組件的基礎(chǔ)上增加了繪制組件的樣式

import { Link, NavLink } from 'react-router-dom'

<Link to="/">Home</Link>

<Link to={{
  pathname: '/posts',
  search: '?sort=name',
  hash:'#the-hash',
  state: { fromHome: true}
}}></Link>

<NavLink to="/me" activeStyle={{SomeStyle}} activeClassName="selected">
  My Profile
</NavLink>

// 重定向
<Redirect to="/register" />

除了使用Link外惕鼓,我們還可以使用 history 對(duì)象手動(dòng)實(shí)現(xiàn)導(dǎo)航。history 中最常用的兩個(gè)方法是 push(path,[state]) 和 replace(path,[state]),push會(huì)向?yàn)g覽器記錄中新增一條記錄唐础,replace 會(huì)用新記錄替換記錄箱歧。

history.push('/posts')
history.replace('/posts')

6)Router Cache

原理:display "block" "none"

1夫否、安裝 react-router-cache-route

yarn add react-router-cache-route --dev

2、使用

import { Route } from 'react-router-dom'
import CacheRoute, { CacheSwitch } from 'react-router-cache-route'

const App = () => {
  return (
    <CacheSwitch>
      <CacheRoute path="/login" exact component={LoginPage} />
      <CacheRoute path="/register" exact component={RegisterPage} />
      <Route component={NotFoundPage} />
    </CacheSwitch>
  )
}

7)例子

// app.jsx

import { HashRouter, Route, Switch } form 'react-router-dom'

const MenuLayout = ({ location }) => (
  <div className="layout">
    <header>
      <p>React Router v4 Browser Example</p>
      <nav>
        <ul>
          <li><Link to="/menu/user">User</Link></li>
          <li><Link to="/menu/order">Order</Link></li>
        </ul>
      </nav>
    </header>
    <div className="container">
      <Switch>
        <Route path="/menu/user" exact component={AboutPage} />
        <Route path="/menu/order" exact component={ProfilePage} />
        <Route render={() => <div>404 Not Found</div>} />
      </Switch>
    </div>
    <footer>
      React Router v4 Browser Example (c) 2017
    </footer>
  </div>
)

const App = () => {
  return (
    <HashRouter>
      <Route path="/login" exact component={LoginPage} />
      <Route path="/register" exact component={RegisterPage} />
      // 不要使用 exact
      <Route path="/menu" component={MenuLayout}></Route>
      <Route component={NotFoundPage} />
    </HashRouter>
  )
}

export default App

四叫胁、 Redux Redux

1) 定義reducer

redux/action-type.js

export const SET_USER_INFO = 'SET_USER_INFO'
export const SET_USER_LIST = 'SET_USER_LIST'
export const SET_NEWS_LIST = 'SET_NEWS_LIST'

redux/actions.js

import { SET_USER_INFO, SET_NEWS_LIST, SET_USER_LIST } from './action-type'

export const setUserInfo = (data) => {
  return {
    type: SET_USER_INFO,
    data
  }
}

export const setUserList = (data) => {
  return {
    type: SET_USER_LIST,
    data
  }
}

export const setNewsList = (data) => {
  return {
    type: SET_NEWS_LIST,
    data
  }
}

redux/reducers/user.js

import { SET_USER_INFO, SET_USER_LIST } from './action-type'

const initialState = {
  userInfo: null,
  userList: []
}
const reducer = (state = initialState, action) = {
  switch(action.type){
    case SET_USER_INFO:
      return Object.assign({}, state, {
        userInfo: action.data
      })
    case SET_USER_LIST:
      return Object.assign({}, state, {
        userList: action.data
      })  
    default:
      return state 
  }
}

export default reducer

redux/reducers/news.js

import { SET_NEWS_LIST } from './action-type'

const initialState = {
  newsList: [],
  total: 0
}
const reducer = (state = initialState, action) = {
  switch(action.type){
    case SET_NEWS_LIST:
      return Object.assign({}, state, {
        newsList: action.data.list,
        total: action.data.total
      })
    default:
      return state  
}
export default reducer

2) 合并reducer redux/reducers/index.js

import {combineReducers} from 'redux'
import user from './user'
import news from './news'

export default combineReducers({
  user,
  news
})

3)創(chuàng)建store redux/index.js

import {createStore} from 'redux'
import reducers from './reducers'

// 創(chuàng)建store對(duì)象
const store = createStore(reducers)

export default store

4)redux connect, 將react與redux關(guān)聯(lián)起來(lái)

connect()接收四個(gè)參數(shù)凰慈,它們分別是mapStateToProps,mapDispatchToProps驼鹅,mergeProps和options微谓。

  • mapStateToProps 將state映射到 UI 組件的參數(shù)(props)

  • mapDispatchToProps 將action映射到 UI 組件的參數(shù)(props)

  • mergeProps 選項(xiàng),如果指定输钩,則定義如何確定自己包裝的組件的最終道具豺型。如果不提供mergeProps,則包裝的組件默認(rèn)會(huì)收到{...ownProps买乃,...stateProps姻氨,...dispatchProps}

  • options 選項(xiàng)

    • [forwardRef = false] 如果已傳遞{forwardRef:true}進(jìn)行連接,則向連接的包裝器組件添加ref實(shí)際上將返回被包裝組件的實(shí)例剪验。默認(rèn)為false

    • ...

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

5) 使用redux

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './redux'
import App from './App'

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

App.js

import { connect } from "react-redux"
import { setUserInfo } from '@/redux/action'

const App = (props) => {
  const { userInfo, setUserInfo, newsList } = props

  const onClick = () => {
    setUserInfo({
      name: 'oo'
    })
  }

  return (
    <div>
      <p>{userInfo.name}</p>
      <button onClick={onClick}>click</button>
      <ul>
        {
          newsList.map((o) => {
            return <li>{o.title}</li>
          })
        }
      </ul>
    </div>
  )
}

App.defaultProps = {
  userInfo: {},
  newsList: []
}

const mapStateToProps = (state) => ({
  userInfo: state.user.userInfo,
  newsList: state.news.newsList
})

const mapDispatchToProps = (dispatch) => ({
  setUserInfo: (data) => dispatch(setUserInfo(data))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  null,
  {forwardRef: true}
)(App)

五肴焊、注意問(wèn)題

1)ant design Form.create 之后如果拿不到 ref

經(jīng)過(guò) Form.create 之后如果要拿到 ref,可以使用 rc-form 提供的 wrappedComponentRef功戚,詳細(xì)內(nèi)容可以查看這里娶眷。

class CustomizedForm extends React.Component { ... }

// use wrappedComponentRef
const EnhancedForm =  Form.create()(CustomizedForm);


class Example extends React.Component {
  render () {
    return (
     <EnhancedForm wrappedComponentRef={(form) => this.form = form} />
    )
  }
}

2) redux connect 之后拿不到ref

connect 之后拿不到ref,配置options.forwardRef=true

class Example extends React.Component { ... }

export default connect(
  mapStateToProps,
  mapDispatchToProps,
  null,
  {forwardRef: true}
)(Example)

資料整理

react

create-react-app

react-app-rewired

customize-cra

react-router

react-redux

classnames

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啸臀,一起剝皮案震驚了整個(gè)濱河市届宠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乘粒,老刑警劉巖豌注,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異灯萍,居然都是意外死亡轧铁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)竟稳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)属桦,“玉大人,你說(shuō)我怎么就攤上這事他爸∧舯觯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵诊笤,是天一觀的道長(zhǎng)系谐。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么纪他? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任鄙煤,我火速辦了婚禮,結(jié)果婚禮上茶袒,老公的妹妹穿的比我還像新娘梯刚。我一直安慰自己,他們只是感情好薪寓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布亡资。 她就那樣靜靜地躺著,像睡著了一般向叉。 火紅的嫁衣襯著肌膚如雪锥腻。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天母谎,我揣著相機(jī)與錄音瘦黑,去河邊找鬼。 笑死奇唤,一個(gè)胖子當(dāng)著我的面吹牛幸斥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冻记,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睡毒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了冗栗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤供搀,失蹤者是張志新(化名)和其女友劉穎隅居,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體葛虐,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胎源,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屿脐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涕蚤。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖的诵,靈堂內(nèi)的尸體忽然破棺而出万栅,到底是詐尸還是另有隱情,我是刑警寧澤西疤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布烦粒,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扰她。R本人自食惡果不足惜兽掰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徒役。 院中可真熱鬧孽尽,春花似錦、人聲如沸忧勿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狐蜕。三九已至宠纯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間层释,已是汗流浹背婆瓜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贡羔,地道東北人廉白。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乖寒,于是被迫代替她去往敵國(guó)和親猴蹂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容