对于现在比较流行的三大框架都有属于自己的脚手架(目前这些脚手架都是使用node编写的,并且都是基于webpack的):
它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好。
注意:项目名称不能使用大写字母
create-react-app
npx create-react-app
全称Progressive Web App,即渐进式WEB应用
解决问题
antd 的 JS 代码默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd'
就会有按需加载的效果
// 1. 安装
npm i antd
// 2. 修改 src/index.js,引入 antd/dist/antd.css
import 'antd/dist/antd.css'
// 3. 使用
import React from 'react'
import { Button } from 'antd'
import './App.css'
const App = () => (
<div className="App">
<Button type="primary">Button</Button>
</div>
)
export default App
// 安装
npm install @craco/craco
/* package.json */
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
安装 craco-less
npm install craco-less
craco.config.js 配置
const CracoLessPlugin = require('craco-less')
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true
}
}
}
}
]
}
index.js 引入
import 'antd/dist/antd.less'
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)
module.exports = {
webpack: {
alias: {
'@': resolve('src')
}
}
}
官网
React Router的版本4开始,路由不再集中在一个包中进行管理了:
安装react-router:
npm install react-router-dom
相当于 vue 的 router-link,渲染成 a 标签。
区别:
NavLink 比 Link 拥有更多的属性,如:exact、className、activeClassName…
<NavLink className="list-group-item" to="/home">Home</NavLink>
/* 封装 */
// MyNavLink 组件
import React from 'react'
import { NavLink } from 'react-router-dom'
// 传过来的 body 内容也在 this.props.children 中
return <NavLink className="list-group-item" activeClassName="linkActive" {...this.props} />
// 使用的组件
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/about">About</MyNavLink>
可切换的路由组件,里面只显示一个路由组件
// v5 版本及之前
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
// v6 版本
<Routes>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Routes>
注意: v6 版本已移除 Switch,改用 Routes
重定向组件
路由渲染组件
import About from './About'
// v5 版本及之前
<Route path="/about" component={About} />
// v6 版本
<Route path="/about" element={<About />} />
相当于 vue 的 router-view,BrowserRouter 是 history 模式,HashRouter 是 hash 模式
区别:
向外暴露 withRouter 包装产生的组件(跟 connect 类似),让非路由组件可以访问到路由组件的 API,内部向组件传递路由组件特有的属性:history/location/match
class NavFooter extends React.Component {
render() {
const navList = router
// 当前请求的路径
const { pathname } = this.props.location
return (
<div>{ pathname }</div>
)
}
}
export default withRouter(NavFooter)
注意: v6 版本已移除
// 父级组件
import React, { PureComponent } from 'react'
import { NavLink, Route, Routes } from 'react-router-dom'
import About from './About'
import Profile from './Profile'
function AboutOne() {
return <div>关1于</div>
}
function History() {
return <div>历1史</div>
}
export default class RouterPage extends PureComponent {
render() {
return (
<>
<NavLink to="/about">关于</NavLink>
<NavLink to="/profile">我的</NavLink>
<Routes>
<Route path="/about/*" element={<About />}>
{/* 此处不能再用 Routes 包裹 */}
<Route exact path="" element={<AboutOne />} />
<Route path="history" element={<History />} />
</Route>
<Route path="/profile" element={<Profile />} />
</Routes>
</>
)
}
}
// About.js
import React, { PureComponent } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default class AboutPage extends PureComponent {
render() {
return (
<>
<div>
<NavLink to="/about">关于</NavLink>
<NavLink to="/about/history">历史</NavLink>
</div>
<Outlet />
</>
)
}
}
/* 动态路由传参 */
// 传数据的组件
<NavLink to={`/home/${1}/标题`}>Home</NavLink>
<Route path="/home/:id/:title" component={Home} />
// 接收数据的组件
render() {
const { id, title } = this.props.match.params
}
// 传数据的组件
<NavLink to='/home?name=sunny&age=18'>Home</NavLink> // 传的参数是公开的
<NavLink to={{
pathname: '/home',
state: {name: 'sunny', age: 18},
search: '?name=sunny&age=18'
}}>Home</NavLink> // 传的参数是加密的
<Route path="/home" component={Home} />
// 接收数据的组件
import qs from 'querystring'
/*
* qs.stringify(obj) 把对象转为路径
* qs.parse(str) 把路径转为对象
*/
render() {
const { search } = this.props.location
const { name, age } = qs.parse(search.slice(1))
const { name } = this.props.location.state
}
相当于 vue 的 $router,常用五个属性如下:
路由管理
npm instaall react-router-config
// router > index.js 定义
import Home from '../pages/home'
import About, { AboutHisotry, AboutCulture, AboutContact, AboutJoin } from '../pages/about'
import Profile from '../pages/profile'
const routes = [
{
path: '/',
exact: true,
component: Home
},
{
path: '/about',
component: About,
routes: [
{
path: '/about',
exact: true,
component: AboutHisotry
},
{
path: '/about/culture',
component: AboutCulture
},
{
path: '/about/contact',
component: AboutContact
},
{
path: '/about/join',
component: AboutJoin
}
]
},
{
path: '/profile',
component: Profile
}
]
export default routes
// 使用
import { renderRoutes } from 'react-router-config'
import routes from '@/router'
// 一级路由出口
{renderRoutes(routes)}
// 二级路由出口
{renderRoutes(this.props.route.routes)}
为什么纯函数在函数式编程中非常重要呢?
单一数据源
改变 state 的唯一途径
加工 state,返回新的 state
import { createStore } from 'redux'
const initState = {
count: 0
}
// 创建 reducer
function reducer(state = initState, action) {
switch (action.type) {
case 'INCREMENT':
return {...state, count: state.count + 1}
default:
return state
}
}
// 创建 store,传入一个 reducer
const store = createStore(reducer)
// 分发 action 前订阅 store 发生变化
store.subscribe(() => {
console.log(store.getState().count) // 1
})
// 分发 action
store.dispatch({ type: 'INCREMENT' })
一般放4个文件
// src > store 文件夹下
// 1. index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
// 2. constants.js 定义 action 常量
export const ADD_NUMBER = 'ADD_NUMBER'
// 3. reducer.js
import { ADD_NUMBER } from './constants'
const initState = {
count: 0
}
export default function reducer(state = initState, action) {
switch (action.type) {
case ADD_NUMBER:
return {...state, count: state.count + action.num}
default:
return state
}
}
// 4. actionCreators.js
import { ADD_NUMBER } from './constants'
export const addNumber = (num) => ({
type: ADD_NUMBER,
num
})
使用
import store from '@/store'
import { addNumber } from '@/store/actionCreators'
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch(addNumber(5))
用来简化 react 应用中使用 redux 的一个插件
Provider
让所有组件都可以得到 state 数据
// index.js
import { Provider } from 'react-redux'
import store from '@/store'
ReactDOM.render(
(
<Provider store={store}>
<App />
</Provider>
),
document.getElementById('root')
)
connect(mapStateToprops, mapDispatchToProps)(Counter)
// 1.引入连接函数
import { connect } from 'react-redux'
// 2.引入 action 函数
import { increment, decrement } from '@/store/actionCreators'
// 3.引入 UI 组件
import Counter from './components/counter'
// 4.向外暴露连接App组件的包装组件
export default connect(
state => ({count: state}),
{ increment, decrement }
)(Counter)
// context.js
import { createContext } from 'react'
export const StoreContext = createContext()
// connect.js
import React, { PureComponent } from 'react'
import { StoreContext } from './context'
export function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
return class extends PureComponent {
static contextType = StoreContext
state = {
storeState: mapStateToProps(this.context.getState())
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState({
storeState: mapStateToProps(this.context.getState())
})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
return <WrappedComponent
{...this.props}
{...mapStateToProps(this.context.getState())}
{...mapDispatchToProps(this.context.dispatch)}
/>
}
}
}
}
// App.js
import React, { PureComponent } from 'react'
import { connect } from '@/utils/connect'
class Home extends PureComponent {
render() {
return (
<>
<div>Home State: {this.props.count}</div>
<hr />
<button onClick={() => this.props.incrementAction()}>+1</button>
</>
)
}
}
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => ({
incrementAction() {
dispatch({ type: 'increment' })
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
// index.js
import store from '@/store'
import { StoreContext } from '@/utils/context'
ReactDOM.render(
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>,
document.getElementById('root')
)
import React, { memo, useEffect } from 'react'
import { useDispatch, useSelector, shallowEqual } from 'react-redux'
import { getTopBannerAction } from './store/actionCreators'
// hook 形式
export default memo(function DiscoverRecommend() {
// shallowEqual 的作用是分析引用的redux数据进行浅层比较, 性能优化
const {topBanners} = useSelector(state => ({
topBanners: state.recommend.topBanners
}), shallowEqual)
console.log(topBanners);
const dispatch = useDispatch()
useEffect(() => {
dispatch(getTopBannerAction())
}, [dispatch])
return (
<div>
DiscoverRecommend
</div>
)
})
默认不支持异步操作(dispatch 只支持传入对象),需安装 redux-thunk 中间件(可以让dispatch支持传入函数)
// store.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer.js'
const store = createStore(reducer, applyMiddleware(thunk))
export default store
第一种写法:
// actionCreators.js
export const incrementAction = dispatch => {
setTimeout(() => {
dispatch({ type: 'INCREMENT' })
}, 300)
}
// App.js
import { incrementAction } from '@/store/actionCreators'
const mapStateToProps = state => ({
count: state.count
})
const mapDispatchToProps = dispatch => ({
incrementAction() => {
dispatch(incrementAction)
}
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
第二种写法(推荐):
// actionCreators.js
export const incrementAction = (num) => {
return dispatch => {
setTimeout(() => {
dispatch({ type: 'INCREMENT', num })
}, 300)
}
}
export const decrementAction = (num) => ({
type: 'DECREMENT',
num
})
// App.js
import { incrementAction, decrementAction } from '@/store/actionCreators'
export default connect(state => ({
count: state.count
}),
{ incrementAction, decrementAction })(Home)
npm install redux-saga
// store.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer'
import saga from './saga'
// 创建sagaMiddleware中间件
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer, composeEnhancers(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(saga)
export default store
// actionCreators.js
export const getList = () => ({
type: 'GETLIST'
})
export const changeBannersAction = (banners) => ({
type: 'CHANGE_BANNERS',
banners
})
export const changeRecommendAction = (recommends) => ({
type: 'CHANGE_RECOMMEND',
recommends
})
// saga.js
import { takeEvery, put, all } from 'redux-saga/effects'
import {
changeBannersAction,
changeRecommendAction
} from './actionCreators'
function* fetchHomeMultidata() {
const res = yield fetch.get('http://127.0.0.1:8000/home/multidata')
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// yield put(changeBannersAction(banners))
// yield put(changeRecommendAction(recommends))
yield all([
yield put(changeBannersAction(banners)),
yield put(changeRecommendAction(recommends))
])
}
function* mySaga() {
// takeLatest takeEvery区别:
// takeLatest: 依次只能监听一个对应的action
// takeEvery: 每一个都会被执行
yield all([
takeEvery('GETLIST', fetchHomeMultidata),
// takeLatest(ADD_NUMBER, fetchHomeMultidata),
])
}
export default mySaga
需先安装 chrome 浏览器插件(Redux Devtools)
npm install --save-dev redux-devtools-extension
使用
// store.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import reducer from './reducer'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
export default store
// store.js
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
export default store
创建包含指定 reducer 的 store 对象
参数
返回值
(Store): 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe 监听 state 的变化,然后更新 UI
import { createStore } from 'redux'
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
const store = createStore(todos, ['Use Redux'])
store.dispatch({
type: 'ADD_TODO',
text: 'Read the docs'
})
console.log(store.getState())
// [ 'Use Redux', 'Read the docs' ]
添加一个变化监听器。更新 UI 视图
// 订阅
const unsubscribe = store.subscribe(() => {
console.log(store.getState())
})
// 取消订阅
unsubscribe()
应用中间件扩展 Redux
整合 reducer,把多个 reducer 函数整合成一个对象
// reducers.js
import { combineReducers } from 'redux'
function counter(state = 0, action) {
console.log('counter', state, action)
}
function comments(state = [], action) {
console.log('comments', state, action)
}
export default combineReducers({
counter,
comments
})
// store.js
import reducers from './reducers'
export default createStore(
reducers,
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)
function createStore(reducer) {
let state = reducer(undefined, {}) // 初始化state
const list = []
function subscribe(callback) { // 收集回调函数
list.push(callback)
}
function dispatch(action) {
state = reducer(state, action)
for (let i in list) { // 执行回调函数
list[i] && list[i]()
}
}
function getState() {
return state
}
return {
subscribe,
dispatch,
getState
}
}
使用React SSR主要有两种方式:
安装Next.js框架的脚手架:
npm install –g create-next-app
创建Next.js项目
create-next-app next-demo
Next.js默认已经给我们配置好了路由映射关系:
方式一:全局样式引入
方式二:module.css
方式三:默认集成styled-jsx
方式四:其他css in js方案,比如styled-components
npm install styled-components
npm install -D babel-plugin-styled-components
路由的嵌套(子路由):
路由的传参:
传递参数有两种办法:
# 1.通过官方工具创建项目
npx @umijs/create-umi-app
# 2.安装依赖
npm install
# 3.启动项目
npm start
umi 会根据 pages 目录自动生成路由配置。需要注释.umirc.js,routes相关,否则自动配置不生效
// pages/index.tsx 重定向到 film
import React from 'react'
import { Redirect } from 'umi'
export default () => <Redirect to="/film"/>
// 在 film 中的 _layout.tsx
import { Redirect } from 'umi'
export default function Film(props) {
const { pathname } = props.location
if (pathname=== '/film' || pathname=== '/film/') {
return <Redirect to="/film/nowplaying" />
}
return <div>{ props.children }</div>
}
// center.tsx
import React from 'react'
const Center = () => {
return <div> <h1>center</h1> </div>
}
Center.wrappers = ['@/wrappers/auth']
export default Center
// wrappers/auth.tsx
import React from 'react'
import { Redirect } from 'umi'
export default (props:any) => {
const isLogin = localStorage.getItem('token')
if (isLogin) {
return <div>{ props.children }</div>
} else {
return <Redirect to="/login" />
}
}
// .umirc.js
export default {
history: {
type: 'hash'
}
}
// models/tabbar.js
export default {
// 命名空间
namespace: 'tabbar',
state: {
isShow: true
},
// 处理state--同步
reducers: {
// reducer 简写, type类型是show的时候自动处理
show(state, {payload}) {
return { ...state, ...payload }
}
},
// yield表示后面的方法执行完以后 call表示调用一个api接口
// put表示一个派发
effects: {
*showEffect(payload, {put}) {
yield put({ type:'show', payload:{ isShow:true } })
}
}
}
import { connect, useDispatch } from 'dva'
function Detail(props) {
const dispatch = useDispatch()
useEffect(() => {
dispatch({ type: "tabbar/showEffect" // 命名空间tabbar })
}, [])
return <div> Detail </div>
}
export default connect(state => state.tabbar)(Detail)
import { getNowplaying } from '@/util/getNowplaying'
export default {
// 命名空间
namespace: 'list',
state: {
list: []
},
// 处理state--同步
reducers: {
changeList(state, {payload}) {
return { ...state, ...payload }
}
},
effects: {
*getListEffect(payload, {put}) {
const res = yield call(getNowplaying, 'test-by-kerwin')
yield put({ type:'changeList', payload:{ list: res } })
}
}
}
// util/getNowplaying
import { fetch } from 'dva' // dva 内置的 fetch
export async function getNowplaying(value) {
console.log(value) // value 是call的第二个参数
const res = await fetch('api')
const result = await res.json()
return result.coming
}