react+redux+router入門(mén)總結(jié)
目錄
- 構(gòu)建配置
- React組件舰涌、css module
- React Router 使用
- Redux Redux 使用
- 注意問(wèn)題
- 資料整理
一、構(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)樣式功能
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)