初识React,React框架入门

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: &nbsp;&nbsp;{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);
}, []);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名字还没想好☜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值