React基础学习

组件实例的三大核心属性

1.state

2.props

3.refs

  1. 字符串形式的ref

    <input ref="input1"/>
    
  2. 回调形式的ref

    <input ref={c=>{this.input1=c}}/>
    
  3. createRef创建ref容器

    myRef=React.createRef()
    <input ref={this.myRef}/>
    

React生命周期(旧)

初始化

1、构造器
    constructor(props){}
2、组件将要挂载
    componentWillMount(){}
3、render
    render(){}
4、组件挂载完毕
    componentDidmount(){}
    卸载组件方法
        ReactDOM.unmountComponentAtNode(document.getElementById(''))
5、组件将要卸载
    componentWillUnmount(){}

更新

(父组件render)
1、组件props将要更新
	componentWillReceiveProps(props){}
(this.setState()或父组件render)
2、是否更新组件
  	shouldComponentUpdate(){
		reutrn <Boolean>
	}
(this.setState()或父组件render或强制更新this.forceUpdate())
3、组件将要更新
	componentWillUpdate(){}
4、render()
5、组件更新完毕
	componentDidUpdate(){}

卸载

1、组件将要卸载
	componentWillUnmount(){}

React生命周期(新)

初始化

由ReactDOM.render()触发——初次渲染
1、constructor()
2、getDerivedStateFromProps
	从props获取派生状态,当state的值在任何时候都取决于props时,可以使用
3、render()
4、componentDidMount()
	组件挂载完毕

更新

由组件内部this.setState()、this.forceUpdate或父组件重新render触发
1、getDerivedStateFromProps
2、shouldComponentUpdate()
    组件是否更新(this.forceUpdate()将会跳过此过程)
3、render()
4、getSnapshotBeforeUpdate()
    更新前获取快照
5、componentDidUpdate()
    组件更新完毕

卸载

由ReactDOM.unmountComponentAtNode()触发
1、componentwillUnmount()

父子组件传值

父传子
  1. 在父组件中使用子组件时,为其传递属性值

    render(){
    	return (
    		<div>
    			<Header title={this.status.title}/>
    		</div>
    	)
    }
    
  2. 在子组件中接收父组件传递过来的属性

    //使用 {this.props.propsName} 的方式接收属性
    render(){
    	return (
    		<div>
    			<h1>{this.props.title}</h1>
    		</div>
    	)
    }
    

    **注意:**当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。

路由

  1. 导航区 通过Link标签创建路由链接

    <Link to="/about">About</Link>
    
  2. 展示区通过Route标签进行路由匹配

    import About from './components/about'
    
    <Route path="/about" component={About} />
    
  3. 使用BrowserRouterHashRouter将所有路由包裹起来

    <BrowserRouter><App/></BrowserRouter>
    

一般组件与路由组件的区别

  1. 写法不同

    //一般组件:
    <Header/>
    //路由组件:
    <Router path="/about" component={About} />
    
  2. 存放的位置不同

    一般组件:components

    路由组件:pages

  3. 收到的props不同

    一般组件:给组件传递什么,就收到什么

    路由组件:收到三个固定的属性

    history:
        go: ƒ go(n)
        goBack: ƒ goBack()
        goForward: ƒ goForward()
        push: ƒ push(path, state)
        replace: ƒ replace(path, state)
    location:
        pathname: "/about"
        search: ""
        state: undefined
    match:
        isExact: true
        params: {}
        path: "/about"
        url: "/about"
    
    

路由标签

Link

创建路由链接

<Link to="/about">About</Link>

NavLink

创建路由链接,通过指定activeClassName,为点击后的导航添加相应类名

<NavLink activeClassName="activeNav" className="list-group-item" to="/about">About</NavLink>

Switch

  1. 通常情况下,path和component是一一对应的关系
  2. Switch可以提高路由的匹配效率(单一匹配)
<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />	//此时只会显示Home组件,不会继续向后匹配路由
  <Route path="/home" component={Test} />
</Switch>

路由严格匹配

//默认模糊匹配	Link链接的路径必须包含Route匹配的路径,且顺序要一致
<Link to="/home/a/b">Home</Link>
<Route path="/home" component={Home} />		//此时home可以被匹配到

//开启严格匹配	exact={true} 简写exact
<Link to="/home/a/b">Home</Link>
<Route exact path="/home" component={Home} />		//此时home无法被匹配到

严格匹配不要随便开启,需要时再开,有些时候开启会导致无法匹配二级路由

路由重定向

放在路由末尾,当所有路由都匹配不上时,重定向到指定路由

<Switch>
  <Route path="/about" component={About} />
  <Route path="/home" component={Home} />
  <Redirect to="/about"/>
</Switch>

嵌套路由

  1. 注册子路由时,需要加上父路由的path

    <Switch>
      <Route path='/home/news' component={News} />
      <Route path='/home/message' component={Message} />
      <Redirect to="/home/news"/>
    </Switch>
    
  2. 路由的匹配是按照注册路由的顺序进行的

    /home->/home/news

路由组件传参

1、params参数

​ 首先,在路由链接Link中传递所需参数,然后在路由Route中声明需要接收的参数,最后在路由组件中,通过this.params.matchparams属性获取params参数。

//Message组件
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>

{/* 声明接收params参数 */}
<Route path='/home/message/detail/:id/:title' component={Detail} />

//Detail组件
render() {
    // 接收params参数
    const {id,title}=this.props.match.params
    const content=data.find(item=>item.id===id).content
    return (
        <ul>
            <li>id:{id}</li>
            <li>title:{title}</li>
            <li>content:{content}</li>
        </ul>
    )
}

2、search参数

  1. 路由链接(携带参数):

    <Link to={`/home/message/detail/?id=01&title=searchParams`}>{item.title}</Link>
    
  2. 正常注册路由(无需声明接收参数)

    <Route path='/home/message/detail' component={Detail} />
    
  3. 接收参数 this.props.location.search

    const {search}=this.props.location		//search = ?id=01&title=searchParams
    //注意:获取到的search是urlencoded编码字符串(key1=value1&key2=value2),需要对其进行解析
    

3、state参数

  1. 路由链接(携带参数):

    <Link to={{pathname:'/home/message/detail',state:{id:'01',title:'stateParams'}}}>{item.title}</Link>
    
  2. 正常注册路由(无需声明接收参数)

    <Route path='/home/message/detail' component={Detail} />
    
  3. 接收参数 this.props.location.state

    const {id,title}=this.props.location.state||{}
    //注意:刷新时可以保留参数,清空缓存直接跳转指定路径时会丢失参数,因为参数保存在浏览器history中
    

push 与 replace模式

通过为Link标签设置replace={true}或简写replace,可以将路由的模式切换为replace,此时切换路由将会替换当前路由

<Link replace to="/home/message/01/params">{item.title}</Link>

编程式导航

  • this.props.history.push(path, state)

  • this.props.history.replace(path, state)

  • this.props.history.go()

  • this.props.history.goBack()

  • this.props.history.goForward()

replaceShow = (id, title) => {
    // replace跳转+params参数
    // this.props.history.replace(`/home/message/detail/${id}/${title}`)

    // replace跳转+search参数
    // this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)

    // replace跳转+state参数
    this.props.history.replace(`/home/message/detail`, { id, title })
}
pushShow = (id, title) => {
    return () => {
        // push跳转+params参数
        // this.props.history.push(`/home/message/detail/${id}/${title}`)

        // push跳转+search参数
        // this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)

        // push跳转+state参数
        this.props.history.push(`/home/message/detail`, { id, title })
    }
}

withRouter

  1. withRouter可以加工一般组件,让一般组件具备路由组件所特有的API

  2. withRouter的返回值是一个新的组件

    import React, { Component } from 'react'
    import { withRouter } from 'react-router-dom' //引入withRouter
    
    class Header extends Component {
        back = () => {
            this.props.history.goBack()	//此时可以使用路由组件上的API
        }
        forward = () => {
            this.props.history.goForward()
        }
        
        render() {
            return (
                <div>
                    <h1>React Router</h1>
                    <button onClick={this.back}>回退</button>
                    <button onClick={this.forward}>前进</button>
                </div>
            )
        }
    }
    export default withRouter(Header)
    

BrowserRouter与HashRouter的区别

  1. 底层原理不一样

    BrowserRouter使用的是H5的history API,不兼容IE9及以下版本

    HashRouter使用的是URL的哈希值

  2. path表现形式不一样

    BrowserRouter路径中没有#,location:3000/home/message

    HashRouter路径中有#,location:3000/#/home/message

  3. 刷新后对路由state参数的影响

    BrowserRouter没有任何影响,因为state保存在history对象中

    HashRouter刷新后会导致路由state参数的丢失

  4. 备注

    HashRouter可以用于解决一些路径错误相关的问题(样式丢失)

其他

this.props.children可以获取标签体内容

类似Vue中的插槽

//Header.jsx
<h1>{this.props.children}</h1>
//使用
<Header>标签体内容</Header>

解决多级路径下刷新页面,样式丢失问题

  1. public/index.html中,引入样式时,不写./xxx,写/xxx (常用)
  2. public/index.html中,引入样式时,不写./xxx,写%PUBLIC_URL%/xxx (常用,适用于React)
  3. 使用HashRouter

redux

redux三个核心概念:

  1. action

    • 动作对象
    • 包含两个属性
      • type:标识属性,值为字符串,唯一,必要属性
      • data:数据属性,值类型任意,可选属性
    • 例子:{type:‘ADD_STUDENT’,data:{name:‘xiaoming’,age:12}}
  2. reducer

    • 用于初始化状态、加工状态
    • 加工时,根据旧的state和action,产生新的state的纯函数
  3. store

    • 将state、action、reducer联系在一起的对象

    • 如何得到此对象?

      import {createStore} from 'redux'
      import reducer from './reducers'
      
      const store = createStore(reducer)
      
    • 此对象的功能?

      1. getState():获取State
      2. dispatch(action):分发action,触发reducer调用,产生新的state
      3. subscribe(listener):注册监听,当产生了新的state时,自动调用

store.getState()

用于获取store中管理的状态

import store from '../../redux/store'

const count = store.getState()

store.dispatch(action)

用于分发action,触发reducer调用

store.dispatch({type:'increment',data:1})

subscribe(listener)

监听state的变化,当state改变时,执行回调函数

import store from './redux/store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<React.StrictMode><App /></React.StrictMode>)

store.subscribe(() => {
    root.render(<React.StrictMode><App /></React.StrictMode>)
})

redux只负责管理状态,状态改变后界面不会自动更新,需要监测状态改变,自行驱动界面重新渲染

异步action

在创建action时,不再返回一个对象,而是返回一个函数。在函数中执行异步任务,之后再通过调用store.dispatch(),去修改state。

需要在store中引入redux-thunk和applyMiddleware,在创建store时,将applyMiddleware(thunk)作为createStore()的第二个参数传入。此时,store接收到函数类型的action时,会去调用该函数。

//count_action.js
// 异步action   返回的action值为函数,异步任务中一般都会调用同步action
export const createIncrementAsyncAction = (data, delay) => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch(createIncrementAction(data))
        }, delay)
    }
}


React拓展

1. setState

setState更新状态的2种写法

	(1). setState(stateChange, [callback])------对象式的setState
            1.stateChange为状态改变对象(该对象可以体现出状态的更改)
            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
					
	(2). setState(updater, [callback])------函数式的setState
            1.updater为返回stateChange对象的函数。
            2.updater可以接收到state和props。
            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
		1.对象式的setState是函数式的setState的简写方式(语法糖)
		2.使用原则:
				(1).如果新状态不依赖于原状态 ===> 使用对象方式
				(2).如果新状态依赖于原状态 ===> 使用函数方式
				(3).如果需要在setState()执行后获取最新的状态数据, 
					要在第二个callback函数中读取

2. lazyLoad

路由组件的lazyLoad

	//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
	const Login = lazy(()=>import('@/pages/Login'))
	
	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
	<Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>

3. Hooks

1. React Hook/Hooks是什么?

(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3. State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

4. Effect Hook

默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
(3). 语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
(4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
    	componentWillUnmount() 

5. Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

4. Fragment

使用

<Fragment><Fragment>
<></>

作用

可以不用必须有一个真实的DOM根标签了


5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

1) 创建Context容器对象:
	const XxxContext = React.createContext()  
	
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
	<xxxContext.Provider value={数据}>
		子组件
    </xxxContext.Provider>
    
3) 后代组件读取数据:

	//第一种方式:仅适用于类组件 
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据
	  
	//第二种方式: 函数组件与类组件都可以
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

注意

在应用开发中一般不用context, 一般都用它的封装react插件

6. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
	注意: 
		只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
		不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

7. render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

9. 组件通信方式总结

组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式:

	1.props:
		(1).children props
		(2).render props
	2.消息订阅-发布:
		pubs-sub、event等等
	3.集中式管理:
		redux、dva等等
	4.conText:
		生产者-消费者模式

比较好的搭配方式:

	父子组件:props
	兄弟组件:消息订阅-发布、集中式管理
	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...