1.react.js
1.React 父子组件通信
//父组件 App.js
import React, { Component } from 'react';
import './App.css';
import Child from './child'
class App extends Component {
constructor(props){
super(props);
this.state={
msg:'父类的消息',
name:'John',
age:99
}
}
callback=(msg,name,age)=>{
// setState方法,修改msg的值,值是由child里面传过来的
this.setState({msg});
this.setState({name});
this.setState({age});
}
render() {
return (
<div className="App">
<p> Message: {this.state.msg}</p>
<Child callback={this.callback} age={this.state.age} name={this.state.name}></Child>
</div>
);
}
}
export default App;
//子组件Child
import React from "react";
class Child extends React.Component{
constructor(props){
super(props);
this.state={
name:'Andy',
age:31,
msg:"来自子类的消息"
}
}
change=()=>{
this.props.callback(this.state.msg,this.state.name,this.state.age);
}
render(){
return(
<div>
<div>{this.props.name}</div>
<div>{this.props.age}</div>
<button onClick={this.change}>点击</button>
</div>
)
}
}
export default Child;
2.immutable
1.为什么要使用immutable
在Rudux中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。
但当你使用immutable数据的时候:只会拷贝你改变的节点,从而达到了节省性能。
总结:immutable的不可变性让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便。
2.immutable的简单实用
安装: cnpm i immutable -S
Map数据结构
immutable.Map():创建一个类似于js中的对象的Map对象
let map = immutable.Map({
name:"Apple",
age:19,
sex:"男"
})
console.log(map); // Map { "name":"Apple", "age":19, "sex":"男" }
1.增
//map.set
let map1 = map.set("sign","呜呜")
console.log(map1); // Map { "name":"Apple", "age":19, "sex":"男", "sign":"呜呜" }
---
//map.setIn
let map1 = map.setIn(["obj","xxx"],"xxx") // 深层的set
console.log(map1); // Map { "name":"Apple", "age":19, "sex":"男", "obj":{ "xxx":"xxx" } }
注意:setIn可以深层操作,第一个参数是个数组,数组中第一个元素是操作的对象的key值,第二个元素是value值,如果不需要可以不用。以下的map.deleteIn、map.updateIn、map.getIn同理。
2.删
map.delete('a') // 删除 a 的值
map.deleteIn(['a', 'b']) // 删除 a 中 b 的值
3.改
//map.update()
> 参数1:需要更新的值
> 参数2:回调函数,返回一个更新后的值
let map1 = map.update('a',function(x){return x+1})
let map2 = map.updateIn(['a', 'b'],function(x){return x+1})
//map.updateIn() 深层更新
> 参数1:一个数组,第一个元素是父元素,第二个元素为目标子元素
> 参数2:回调函数,参数为目标值的值,返回值为一个更新后的值
let map1 = map.update('a',function(x){return x+1})
let map2 = map.updateIn(['a', 'b'],function(x){return x+1})
4.查
返回的不是immutable对象了 而是里边定义的正常值
map.get('a') // {a:1} 得到1。
map.getIn(['a', 'b']) // {a:{b:2}} 得到2。
List数据结构
immutable.List():创建一个类似于js中的数组的List对象
let list = immutable.List([1,2,3,4,5])
增
list.push(6)
list.splice(0,0,10)
用法和js的push一样,但是返回值为immutable的List结构,而不是数组
删
list.splice(1,1)
改
list.splice(1,1,10)
查
list.getIn([0])
API
merge():合并map对象
let newMap = map.merge(map1)
1
toObject():immutable的map对象转JS对象
浅转换,只转换最外层
toArray():immutable的list对象转JS数组
浅转换,只转换最外层
toJS():immutable的 map对象/list对象 转 JS对象/JS数组
深转换,全部转换,更耗费性能。
Map():JS对象或数组转换成immutable
浅转换,只转换最外层
fromJS():JS对象/JS数组 转换成immutable
深转换,全部转换,更耗费性能。
2.react路由
组件内用法
import React,{ Component } from "react";
import { BrowserRouter,Route,Switch } from "react-router-dom";
import routers from "./router/index";
class App extends Component<any,any>{
render(): React.ReactNode {
return (
<BrowserRouter>
<Switch>
{
routers.map(router=>{
return (
<Route
exact
path={router.path}
component = { router.component }
></Route>
)
})
}
</Switch>
</BrowserRouter>
)
}
}
export default App
//router/index.tsx 代码
import Admin from "../pages/admin";
import Home from "../pages/home";
import User from "../pages/user";
import UserTwo from "../pages/usertow";
import Demo1 from "../pages/routerDemo/demo1";
import Demo2 from "../pages/routerDemo/demo2";
import Demo3 from "../pages/routerDemo/demo3";
interface router {
path:string,
component:any,
children?:Array<router>
}
const routers:Array<router> = [
{
path:'/',
component:Admin,
children:[
{
path:'/demo1',
component:Demo1
},
{
path:'/demo2',
component:Demo2
},
{
path:'/demo3',
component:Demo3
}
]
},
{
path:'/home',
component:Home
},
{
path:'/user',
component:User
},
{
path:'/:userId',
component:UserTwo
}
]
export default routers
1.路由传参与接收
方式1 通过params
<Route path='/:userId' component={User}></Route>
//跳转路由
this.props.history.push('/1234')
//接收
this.props.match.params.userId
方式2 通过query
//跳转路由
this.props.history.push({ pathname: '/home' , query : { id: '6666' }})
//接收
this.props.location.query.id
方式3 通过state
//跳转路由
this.props.history.push({ pathname: '/home' , state: { id: '6666' }})
//接收
this.props.location.state.id
2.路由跳转
1. Switch组件
它的作用是只渲染出第一个与当前访问地址匹配的和组件
2. Route组件
1.当地址URL和path属性设置的值匹配时,渲染出相应的UI组件界面;
基本用法:<Route exact path="/">
2.Route render methods
用法:
1.
<Route exact path="/" component={MyPage}>
2.
<Route path="/home" render={() => {
console.log('额外的逻辑');
return (<div>Home</div>);
}/>
3.参数
所有路由中指定的组件将被传入以下三个 props :location、history、match
1.获取location
- 在 Route component 中,以 this.props.location 的方式获取
- 在 Route render 中,以 ({ location }) => () 的方式获取
- 在 Route children 中,以 ({ location }) => () 的方式获取
- 在 withRouter 中,以 this.props.location 的方式获取
2.history
- history 对象通常会具有以下属性和方法:
-
- length -( number 类型)指的是 history 堆栈的数量。
- action -( string 类型)指的是当前的动作(action),例如 PUSH,REPLACE 以及 POP 。
- location -( object类型)是指当前的位置(location),location 会具有如下属性:
- * pathname -( string 类型)URL路径。
- * search -( string 类型)URL中的查询字符串(query string)。
- * hash -( string 类型)URL的 hash 分段。
- * state -( string 类型)是指 location 中的状态,例如在 push(path, state) 时,state会描述什么时候 location 被放置到堆栈中等信息。这个
- state 只会出现在 browser history 和 memory history 的环境里。
- push(path, [state]) -( function 类型)在 hisotry 堆栈顶加入一个新的条目。
- replace(path, [state]) -( function 类型)替换在 history 堆栈中的当前条目。
- go(n) -( function 类型)将 history 堆栈中的指针向前移动 n 。
- goBack() -( function 类型)等同于 go(-1) 。
- goForward() -( function 类型)等同于 go(1) 。
- block(prompt) -( function 类型)阻止跳转
3.获取location
- params -( object 类型)即路径参数,通过解析URL中动态的部分获得的键值对。
- isExact - 当为 true 时,整个URL都需要匹配。
- path -( string 类型)用来做匹配的路径格式。在需要嵌套 Route 的时候用到。
- url -( string 类型)URL匹配的部分,在需要嵌套 Link 的时候会用到。
- 你可以在以下地方获取 match 对象
-
- 在 Route component 中,以 this.props.match 方式。
- 在 Route render中,以 ({ match }) => () 方式。
- 在 Route children中,以 ({ match }) => () 方式
3.NavLink组件
主要用于导航拥有激活状态准备的;它和Link的路由匹配效果一致;不同的是NavLink有状态标记,Link无状态标记,如下面效果实现就建议使用NavLink;
用法:
<NavLink to="/one" activeClassName="actived"></NavLink>
4.嵌套路由
1.创建一个home/home.tsx页面 一个login/login.tsx页面home.tsx
//home.tsx
import { Layout, Menu } from 'antd';
import {
MenuUnfoldOutlined,
MenuFoldOutlined,
UserOutlined,
VideoCameraOutlined,
UploadOutlined,
} from '@ant-design/icons';
import React from "react";
import './home.scss'
const { Header, Sider, Content } = Layout;
class Home extends React.Component<any,any> {
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
render() {
return (
<Layout className='Body'>
<Sider trigger={null} collapsible collapsed={this.state.collapsed}>
<div className="logo" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1" icon={<UserOutlined />}>
nav 1
</Menu.Item>
<Menu.Item key="2" icon={<VideoCameraOutlined />}>
nav 2
</Menu.Item>
<Menu.Item key="3" icon={<UploadOutlined />}>
nav 3
</Menu.Item>
</Menu>
</Sider>
<Layout className="site-layout">
<Header className="site-layout-background" style={{ padding: 0 }}>
{React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: this.toggle,
})}
</Header>
<Content
className="site-layout-background"
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
}}
>
{ this.props.children }
</Content>
</Layout>
</Layout>
);
}
}
export default Home
//login.tsx
import React,{ Component } from "react";
import { Button } from 'antd'
class Login extends Component<any, any>{
goHome(){
this.props.history.push('/home')
}
render(): React.ReactNode {
return (
<div>
这是登录页
<Button type='primary' onClick={this.goHome.bind(this)}>跳转首页</Button>
</div>
)
}
}
export default Login
2.修改路由router/index.ts
import Login from "../pages/login/Login";
import Home from "../pages/home/home";
import User from "../pages/user";
import UserTwo from "../pages/usertow";
import Demo1 from "../pages/routerDemo/demo1";
import Demo2 from "../pages/routerDemo/demo2";
import Demo3 from "../pages/routerDemo/demo3";
interface router {
path:string,
component:any,
exact?:boolean,
children?:Array<router>
}
const routers:Array<router> = [
{
path:'/',
exact:true,
component:Login
},
{
path:'/home',
component:Home,
children:[
{
path:'/',
component:Demo1
},
{
path:'/home/demo2',
component:Demo2
},
{
path:'/home/demo3',
component:Demo3
}
]
},
{
path:'/home',
component:Home
},
{
path:'/user',
component:User
},
{
path:'/user/:userId',
component:UserTwo
}
]
export default routers
3.App.tsx
import React,{ Component } from "react";
import { BrowserRouter,Route,Switch } from "react-router-dom";
import './App.css'
import routers from "./router/index";
class App extends Component<any,any>{
render(): React.ReactNode {
return (
<BrowserRouter>
<Switch>
{
routers.map((router,index)=>{
return (
<Route
exact={ router.exact }
key={index}
path={router.path}
render={ (props)=>{
return (
<div>
<router.component { ...props }>
{
router.children?.map((item,itemIndex)=>{
return (
<Route
exact={ item.exact }
key={itemIndex}
path={item.path}
component = { item.component }
/>
)
})
}
</router.component>
</div>
)
} }
/>
)
})
}
</Switch>
</BrowserRouter>
)
}
}
export default App
警告:<Route component> 优先于 <Route render>,因此不要在同一个 <Route> 中同时使用两者。
4.使用render render后面跟一个函数 可以获取到参数props
render={ (props)=>{
return (
<div>
<router.component { ...props }>//这里要把props绑定上
{
router.children?.map((item,itemIndex)=>{
return (
<Route
exact={ item.exact }
key={itemIndex}
path={item.path}
component = { item.component }
/>
)
})
}
</router.component>
这里还可以用render传参的方式 到父页面去渲染
最后我们到父页面 在需要渲染子路由的地方 添加
{ this.props.children }
这个就可以子路由渲染到这里 跟vue的<router-view></router-view>效果一样
3.react-redux
1.封装react-redux
1.actionCreator.js
import * as actionTypes from './actionTypes'
import {
getTopBanners,
getHotRecommends,
getNewAlbums,
getSettleSinger,
} from '@/service/recommend.js'
import { getToplistDetail } from '@/service/toplist'
// 轮播图Action
export const changeTopBannerAction = res => ({
type: actionTypes.CHANGE_TOP_BANNER,
topBanners: res.banners,
})
// 热门推荐Action
export const changeHotRecommendAction = res => ({
type: actionTypes.CHANGE_HOT_RECOMMEND,
hotRecommends: res.result,
})
// --------------------------------------------------------------
// 发送网络请求将结果传递给派发的Action中 (react-redux可以让该函数返回一个函数而不是返回一个对象: redux-thunk使用)
// 轮播图network request
export const getTopBannersAction = () => {
return dispatch => {
// 发送网络请求
getTopBanners().then(res => {
dispatch(changeTopBannerAction(res))
})
}
}
// 热门推荐network request
export const getHostBannersAction = limit => {
return dispatch => {
getHotRecommends(limit).then(res => {
dispatch(changeHotRecommendAction(res))
})
}
}
2.actionType.js
export const CHANGE_TOP_BANNER = 'CHANGE_TOP_BANNER'
export const CHANGE_HOT_RECOMMEND = 'CHANGE_HOT_RECOMMEND'
export const CHANGE_NEW_ALBUMS = 'CHANGE_NEW_ALBUMS'
export const CHANGE_UP_RANKING = 'CHANGE_UP_RANKING'
export const CHANGE_NEW_RANKING = 'CHANGE_NEW_RANKING'
export const CHANGE_ORIGIN_RANKING = 'CHANGE_ORIGIN_RANKING'
export const CHANGE_SETTLE_SINGER = 'CHANGE_SETTLE_SINGER'
3.reducer
import { Map } from 'immutable'
import * as actionTypes from './actionTypes'
// 使用Immutable管理redux中的state (修改的`state`不会修改原有数据结构, 而是返回修改后新的数据结构)
const defaultState = Map({
topBanners: [],
hotRecommends: [],
newAlbums: [],
upRanking: {},
newRanking: {},
originRanking: {},
settleSinger: []
})
function reducer(state = defaultState, action) {
switch (action.type) {
case actionTypes.CHANGE_TOP_BANNER:
return state.set('topBanners', action.topBanners)
case actionTypes.CHANGE_HOT_RECOMMEND:
return state.set('hotRecommends', action.hotRecommends)
case actionTypes.CHANGE_NEW_ALBUMS:
return state.set('newAlbums', action.newAlbums)
case actionTypes.CHANGE_UP_RANKING:
return state.set('upRanking', action.upRanking)
case actionTypes.CHANGE_NEW_RANKING:
return state.set('newRanking', action.newRanking)
case actionTypes.CHANGE_ORIGIN_RANKING:
return state.set('originRanking', action.originRanking)
case actionTypes.CHANGE_SETTLE_SINGER:
return state.set('settleSinger', action.settleSinger)
default:
return state
}
}
export default reducer
4.store中index.js
import reducer from './reducer'
export {
reducer
}
5.统一到全局reducer
import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store';
import { reducer as playerReducer } from '../pages/player/store';
import { reducer as toplistReducer } from '../pages/discover/child-pages/toplist/store';
import { reducer as songsReducer } from '../pages/discover/child-pages/songs/store';
import { reducer as themeHeaderReducer } from '@/components/app-header/store';
import { reducer as searchReducer } from '@/pages/search/store'
import { reducer as songDetailRducer } from '@/pages/song-detail/store'
import { reducer as loginReducer } from '@/components/theme-login/store'
// 多个reducer合并
const cRducer = combineReducers({
recommend: recommendReducer,
player: playerReducer,
toplist: toplistReducer,
songList: songsReducer,
themeHeader: themeHeaderReducer,
search: searchReducer,
songDetail: songDetailRducer,
loginState: loginReducer
});
export default cRducer;
6.全局store的index.js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk'
import cRducer from "./reducer";
// redux-devtools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(cRducer, composeEnhancers(
applyMiddleware(thunk)
))
export default store
2.使用
1.入口使用Provider组件全局使用
import store from './store';
import { Provider } from 'react-redux';
<Provider store={store}>
<APP/>
</Provider>
2.组件内的使用
1.方式一 使用connect
import React, { Component } from 'react'
import PropTypes from 'prop-types' //类型检查
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// 定义counter组件
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}> +1</button>
</div>
)
}
}
//对Counter组件接受的props进行类型检查
Counter.propTypes = {
value: PropTypes.number.isRequired, //要求数字类型,没有提供会警告
onIncreaseClick: PropTypes.func.isRequired //要求函数类型
}
// Action
const increaseAction = { type: 'increase' }
// Reducer 基于原有state根据action得到新的state
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
// 根据reducer函数通过createStore()创建store
const store = createStore(counter)
// 将state映射到Counter组件的props
function mapStateToProps(state) {
return {
value: state.count
}
}
// 将action映射到Counter组件的props
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// 传入上面两个函数参数,将Counter组件变为App组件
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
1.方式二 使用hook
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
import { getHostBannersAction } from '../../store/actionCreator'
const [offset, setOffset] = useState(0);
// redux hook
//需要安装immutable
//cnpm i immutable -S
//mmutable是一种持久化数据。一旦被创建就不会被修改。修改immutable对象的时候返回新的immutable。但是原数据不会改变。
const { songList } = useSelector(
(state) => ({
songList: state.getIn(['songList', 'songList']),
}),
shallowEqual
);
/*
const { songList } =
(state) => ({
songList: state.songList
}),
*/
const dispatch = useDispatch();
// other hook
useEffect(() => {
dispatch(getSongListAction(SONG_LIST_LIMIT, 0));
}, [dispatch]);
// offset改变派发action
useEffect(() => {
dispatch(getSongListAction(SONG_LIST_LIMIT, offset));
}, [offset, dispatch]);
const changePage = useCallback((currentPage) => {
// offset=(当前页数-1)*limit
const targePageCount = (currentPage - 1) * SONG_LIST_LIMIT;
setOffset(targePageCount);
window.scroll(0, 0);
}, []);