react+redux+router入门总结

react+redux+router入门总结

目录

  1. 构建配置
  2. React组件、css module
  3. React Router 使用
  4. Redux Redux 使用
  5. 注意问题
  6. 资料整理

一、构建配置

1)使用 react-app-rewired 对 create-react-app 的默认配置进行自定义

1、 引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 react-app-rewired@2.x 版本的关系,你还需要安装 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、根目录创建 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]', // 开启less module
    modifyVars: { // less 变量
      '@primary-color': '#0999aa',
      '@success-color': '#45A767',
      '@layout-header-background': '#0999aa'
    }
  }),
  addWebpackAlias({
    "@": path.resolve(__dirname, "src")
  }),
  addWebpackConfig()
)

2)PUBLIC_URL

1、添加环境变量到build, dev访问需要配合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)有状态组件 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)无状态函数组件

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 中对组件逻辑复用部分进行抽离的高级技术,但HOC并不是一个 React API 。 它只是一种设计模式,类似于装饰器模式。

在 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中, 因为此ref是保留字段,需要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

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

// 调用
// 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是有状态的函数

hook作用: <b>将可复用的最小单元从组件层面进一步细化到逻辑层面。状态逻辑和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('计数完成')
    }
  }, [])
  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动态样式功能

classnames

1、安装

yarn add classnames

2、使用方法

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

// 其他类型
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

  • 文件名规则 xxx.module.less, 必须以.module.less结尾
  • 命名:可以使用驼峰或者连接线命名

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) 安装

创建 Web应用,使用

yarn add react-router-dom

创建 navtive 应用,使用

yarn add react-router-native

2) 路由模式

BrowserRouter模式 创建的 URL 形式如下:

http://example.com/some/path

HashRouter模式 创建的 URL 形式如下:

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

3) Route组件

<Route>组件是react router v4里最有用的组件。无论何时你需要在匹配某个路径的时候绘制一个组件,那么就可以使用Route组件。

Route组件可以使用如下的属性:

  • path属性,字符串类型,它的值就是用来匹配url的。
  • component属性,它的值是一个组件。在path匹配成功之后会绘制这个组件。
  • exact属性,这个属性用来指明这个路由是不是排他的匹配 (绝对匹配)。
  • strict属性, 这个属性指明路径只匹配以斜线结尾的路径。

还有其他的一些属性,可以用来代替component属性

  • render属性,一个返回React组件的方法,只要你的路由匹配了,这个函数才会执行
  • children属性,返回一个React组件的方法。只不过这个总是会绘制,即使没有匹配的路径的时候。

使用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组件

只有第一个匹配的路由<Route>或者<Redirect>会被绘制,匹配到一个就不再匹配

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属性,这个属性的值就是react router要跳转到的地址

NavLink是Link的一个子类,在Link组件的基础上增加了绘制组件的样式

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 对象手动实现导航。history 中最常用的两个方法是 push(path,[state]) 和 replace(path,[state]),push会向浏览器记录中新增一条记录,replace 会用新记录替换记录。

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)创建store redux/index.js

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

// 创建store对象
const store = createStore(reducers)

export default store

4)redux connect, 将react与redux关联起来

connect()接收四个参数,它们分别是mapStateToProps,mapDispatchToProps,mergeProps和options。

  • mapStateToProps 将state映射到 UI 组件的参数(props)

  • mapDispatchToProps 将action映射到 UI 组件的参数(props)

  • mergeProps 选项,如果指定,则定义如何确定自己包装的组件的最终道具。如果不提供mergeProps,则包装的组件默认会收到{...ownProps,...stateProps,...dispatchProps}

  • options 选项

    • [forwardRef = false] 如果已传递{forwardRef:true}进行连接,则向连接的包装器组件添加ref实际上将返回被包装组件的实例。默认为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)

五、注意问题

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

经过 Form.create 之后如果要拿到 ref,可以使用 rc-form 提供的 wrappedComponentRef,详细内容可以查看这里

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容