React学习1

简介: 多次setState只会进行一次render,会把所有setState合并再进行render

1.React原理

1.1 setState

多次setState只会进行一次render,会把所有setState合并再进行render



推荐语法

用两个setState会不一样,会加2,而原来的语法则是只加1



setState的第二个参数


this.setState(
      (state, props) => {
        return {
          count: state.count + 1
        }
      },
      // 状态更新后并且重新渲染后,立即执行:
      () => {
        console.log('状态更新完成:', this.state.count) // 2
        console.log(document.getElementById('title').innerText)
        document.title = '更新后的count为:' + this.state.count
      }
    )
    console.log(this.state.count) // 1

1.2 JSX的转化原理


1.3 组件更新机制


1.4 组件性能优化

1.4.1减轻state


1.4.2  避免不必要的重新渲染


class App extends React.Component {
  state = {
    count: 0
  }
  handleClick = () => {
    this.setState(state => {
      return {
        count: state.count + 1
      }
    })
  }
  // 钩子函数
  shouldComponentUpdate(nextProps, nextState) {
    // 返回false,阻止组件重新渲染
    // return false
    // 最新的状态:
    console.log('最新的state:', nextState)
    // 更新前的状态:
    console.log('this.state:', this.state)
    return true
  }
  render() {
    console.log('组件更新了')
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}


1.5 纯组件


纯组件会先进行一次比较,如果两次对象都是同一个即使内部数据改变,则不会发生渲染




2.虚拟DOM和diff算法


2.1 虚拟DOM



3.React路由


3.1 安装router

npm add react-router-dom

3.2 导入三个核心组件Router,Route,Link

(下面BrowserRouter as Router 是给BrowserRouter起别名叫做Router)

import {BrowserRouter as Router,Route,Link} from 'react-router-dom';

3.3 Router组件包裹整个应用

const App= ()=> {
  <Router>
    <div>
      <h1>React路由</h1>
    </div>
  </Router>
}

3.4 使用Link组件作为导航菜单(路由入口)

<Link to="/first">页面一</Link>

3.4.1 NavLink组件

NavLink可以实现路由链接的高亮,通过activeClassName指定样式名


当点击该NavLink组件则该NavLink组件会追加一个类名active,可以通过activeClassName改为其他名称,如图中改为atguigu

<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>

当我们NavLink都有同样的样式,都要写一个同样的类名,一个个这样写出来不简洁,于是我们便把NavLink封装起来

import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
  render() {
  // console.log(this.props);
  return (
    <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
  )
  }
}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>

3.5 使用Router组件配置路由规则和要展示的组件(路由出口)

const First =() => <p>页面一的内容</p>
const App= ()=> (
  <Router>
    <div>
      <h1>React路由</h1>
      <Link to="/first">页面一</Link>
      <Route path="/first" component={First}></Route>
    </div>
  </Router>
)

3.6 常用组件说明



3.7 路由执行过程

3.8 编程式导航


3.9 默认路由


3.10.1 模糊匹配模式


3.10.2 精确匹配


3.11 Switch组件

1.通常情况下,path和component是一一对应的关系。


2.Switch可以提高路由匹配效率(单一匹配)。


3.当路由匹配成功便不会往下继续查找


将Route组件全部包裹进Switch组件中

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

3.12 Redirect组件

一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由


<Switch>

<Route path="/about" component={About}/>

<Route path="/home" component={Home}/>

<Redirect to="/about"/>

</Switch>

3.13 向路由组件传递参数

3.13.1 params参数

             路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>


             注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>


             接收参数:this.props.match.params


<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<Route path="/home/message/detail/:id/:title" component={Detail}/>
// 接收params参数
const {id,title} = this.props.match.params

 3.13.2 search参数

             路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>


             注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>


             接收参数:this.props.location.search


             备注:获取到的search是urlencoded编码字符串,需要借助querystring解析


<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
//引入qs用于将接受的参数字符串转化为对象
import qs from 'querystring'
// 接收search参数
const {search} = this.props.location
//slice(1)是为了将字符串第一个字符‘?’去掉
const {id,title} = qs.parse(search.slice(1))

3.13. 3 state参数

             路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>


             注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>


             接收参数:this.props.location.state


             备注:刷新也可以保留住参数


<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
// 接收state参数
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {}

3.14 withRouter

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


withRouter的返回值是一个新组件

import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
  back = ()=>{
  this.props.history.goBack()
  }
  forward = ()=>{
  this.props.history.goForward()
  }
  go = ()=>{
  this.props.history.go(-2)
  }
  render() {
  console.log('Header组件收到的props是',this.props);
  return (
    <div className="page-header">
    <h2>React Router Demo</h2>
    <button onClick={this.back}>回退</button>&nbsp;
    <button onClick={this.forward}>前进</button>&nbsp;
    <button onClick={this.go}>go</button>
    </div>
  )
  }
}
export default withRouter(Header)


4 ref

组件内的标签可以定义ref属性来标识自己


字符串形式(过时的api)

class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}


回调函数形式(react会自动帮你调用这个函数)

class Demo extends React.Component{
//展示左侧输入框的数据
showData = ()=>{
const {input1} = this
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
const {input2} = this
alert(input2.value)
}
render(){
return(
<div>
<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>&nbsp;
<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
</div>
)
}
}


但是上面的方式每一次刷新组件都会调用两次这个内联函数,下面代码则可以避免每次刷新都重新调用这个内联函数(其实以后要用依旧可以用内联函数,这两次调用无关紧要)


class Demo extends React.Component{
    state = {isHot:false}
    showInfo = ()=>{
    const {input1} = this
    alert(input1.value)
    }
    changeWeather = ()=>{
    //获取原来的状态
    const {isHot} = this.state
    //更新状态
    this.setState({isHot:!isHot})
    }
    saveInput = (c)=>{
    this.input1 = c;
    console.log('@',c);
    }
    render(){
    const {isHot} = this.state
    return(
      <div>
      <h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
      {/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
      <input ref={this.saveInput} type="text"/><br/><br/>
      <button onClick={this.showInfo}>点我提示输入的数据</button>
      <button onClick={this.changeWeather}>点我切换天气</button>
      </div>
    )
    }
  }

createRef形式


React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的(即一个节点只能用一个容器)

class Demo extends React.Component{
  /* 
  React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
  */
  myRef = React.createRef()
  myRef2 = React.createRef()
  //展示左侧输入框的数据
  showData = ()=>{
  alert(this.myRef.current.value);
  }
  //展示右侧输入框的数据
  showData2 = ()=>{
  alert(this.myRef2.current.value);
  }
  render(){
  return(
    <div>
    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
    <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
    </div>
  )
  }
}

5 redux

中文文档: http://www.redux.org.cn/


英文文档: https://redux.js.org/


5.1 redux是什么?

redux是一个专门用于做状态管理的JS库(不是react插件库)。

它可以用在react, angular, vue等项目中, 但基本与react配合使用。

作用: 集中式管理react应用中多个组件共享的状态。

5.2 什么情况下需要使用redux

某个组件的状态,需要让其他组件可以随时拿到(共享)。

一个组件需要改变另一个组件的状态(通信)。

总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

5.3  redux的三个核心概念

5.3.1 action

动作的对象

包含2个属性

type:标识属性, 值为字符串, 唯一, 必要属性

data:数据属性, 值类型任意, 可选属性

例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

5.3.2  reducer

用于初始化状态、加工状态。

加工时,根据旧的state和action, 产生新的state的纯函数。

5.3.3 store

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


2. 如何得到此对象?


   (1)import {createStore} from 'redux'


   (2)import reducer from './reducers'


   (3)const store = createStore(reducer)


3. 此对象的功能?


   (1)  getState(): 得到state


   (2)  dispatch(action): 分发action, 触发reducer调用, 产生新的state


   (3)  subscribe(listener): 注册监听, 当产生了新的state时, 自动调用


5.4 redux 核心API

5.4.1  createstore()

作用:创建包含指定reducer的store对象


5.4.2. store对象


5.4.3  applyMiddleware()

作用:应用上基于redux的中间件(插件库)


5.4.4 combineReducers()

作用:合并多个reducer函数


5.5 redux异步编程

5.5.1理解:

redux默认是不能进行异步处理的,

某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

5.5.2. 使用异步中间件


npm install --save redux-thunk

5.6 使用步骤

安装redux


yarn add redux

生成随机id


npm add nanoid

1.求和案例_redux精简版


   (1).去除Count组件自身的状态


   (2).src下建立:


           -redux


             -store.js


             -count_reducer.js


   (3).store.js:


         1).引入redux中的createStore函数,创建一个store


         2).createStore调用时要传入一个为其服务的reducer


         3).记得暴露store对象


   (4).count_reducer.js:


         1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态


         2).reducer有两个作用:初始化状态,加工状态


         3).reducer被第一次调用时,是store自动触发的,


                 传递的preState是undefined,


                 传递的action是:{type:'@@REDUX/INIT_a.2.b.4}


   (5).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>


       备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。


2.求和案例_redux完整版


新增文件:


     1.count_action.js 专门用于创建action对象


     2.constant.js 放置容易写错的type值


3.求和案例_redux异步action版    


(1).明确:延迟的动作不想交给组件自身,想交给action


    (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。


    (3).具体编码:


         1).yarn add redux-thunk,并配置在store中


         2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。


         3).异步任务有结果后,分发一个同步的action去真正操作数据。


    (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。


5.7 react-redux


5.7.1 求和案例_react-redux基本使用

     (1).明确两个概念:


           1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。


           2).容器组件:负责和redux通信,将结果交给UI组件。


     (2).如何创建一个容器组件————靠react-redux 的 connect函数


             connect(mapStateToProps,mapDispatchToProps)(UI组件)


               -mapStateToProps:映射状态,返回值是一个对象


               -mapDispatchToProps:映射操作状态的方法,返回值是一个对象


     (3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入


     (4).备注2:mapDispatchToProps,也可以是一个对象


5.7.2 求和案例_react-redux优化

 (1).容器组件和UI组件整合一个文件


     (2).无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。


     (3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。


     (4).mapDispatchToProps也可以简单的写成一个对象


     (5).一个组件要和redux“打交道”要经过哪几步?


             (1).定义好UI组件---不暴露


             (2).引入connect生成一个容器组件,并暴露,写法如下:


                 connect(


                   state => ({key:value}), //映射状态


                   {key:xxxxxAction} //映射操作状态的方法


                 )(UI组件)


             (4).在UI组件中通过this.props.xxxxxxx读取和操作状态


5.7.3  求和案例_react-redux数据共享版

(1).定义一个Pserson组件,和Count组件通过redux共享数据。


     (2).为Person组件编写:reducer、action,配置constant常量。


     (3).重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,


         合并后的总状态是一个对象!!!


     (4).交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。


5.7.4 求和案例_react-redux开发者工具的使用

(1).yarn add redux-devtools-extension


     (2).store中进行配置


         import {composeWithDevTools} from 'redux-devtools-extension'


         const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))


5.7.5  求和案例_react-redux最终版

(1).所有变量名字要规范,尽量触发对象的简写形式。


(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer


6.拓展

6.1 lazyload

路由懒加载


用这种方法引入路由组件


import React, { Component,lazy,Suspense} from 'react

import Loading from './Loading'

const Home = lazy(()=> import('./Home') )

const About = lazy(()=> import('./About'))

注册路由(当网速慢加载不出来就显示Loading组件)


<Suspense fallback={<Loading/>}>
  {/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Suspense>

6.2 Hooks

6.2.1 React Hook/Hooks是什么?

(1). Hook是React 16.8.0版本增加的新特性/新语法


(2). 可以让你在函数组件中使用 state 以及其他的 React 特性


6.2.2 State Hook

State Hook 里面就两个参数,第一个是存放数值,第二个是更新数值的方法(数组解构可以通过位置来解构,不用同一名称)


const [count,setCount] = React.useState(0)

useState()说明:


参数: 第一次初始化指定的值在内部作缓存


返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数


setCount(count+1) //第一种写法

setCount(count => count+1 )

6.2.3 Effect Hook

Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)


当useEffect第二个参数为空数组时,只相当于 componentDidMount(),当没有第二个参数,则监视所有的state的数据更新,每次数据更新就重新更新一次组件,第二个参数中如果写了指定参数,就只监视指定参数的更新,也相当于componentDidUpdate(),当useEffect里返回了一个函数,这个函数就相当于componentWillUnmount()


React.useEffect(()=>{

let timer = setInterval(()=>{

setCount(count => count+1 )

},1000)

return ()=>{

clearInterval(timer)

}

},[])

6.2.4 Ref Hook

const myRef = React.useRef()

6.3 Fragment

每用一个组件就得在组件最外层包裹一层div,这样堆叠的层级太多了,我们可以用Fragment组件


React识别到它时会丢弃它,从而少一层div,

import React, { Component,Fragment } from 'react'
export default class Demo extends Component {
  render() {
  return (
    <Fragment key={1}>
    <input type="text"/>
    <input type="text"/>
    </Fragment>
  )
  }
}

其实也可以用空标签(但是它俩还是有区别,Fragment可以加而且只能加key属性,当这个组件需要遍历时就可以用Fragment组件)

<>
  <input type="text"/>
  <input type="text"/>
 </>

6.4 Context

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


在祖组件和后代组件都能接触的地方创建Context对象


//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
export default class A extends Component {
  state = {username:'tom',age:18}
  render() {
  const {username,age} = this.state
  return (
    <div className="parent">
    <h3>我是A组件</h3>
    <h4>我的用户名是:{username}</h4>
    <Provider value={{username,age}}>
      <B/>
    </Provider>
    </div>
  )
  }
}

类组件接收方式


class C extends Component {
  //声明接收context
  static contextType = MyContext
  render() {
  const {username,age} = this.context
  return (
    <div className="grand">
    <h3>我是C组件</h3>
    <h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
    </div>
  )
  }
}

函数组件接收方式

function C(){
  return (
  <div className="grand">
    <h3>我是C组件</h3>
    <h4>我从A组件接收到的用户名:
    <Consumer>
    {value => `${value.username},年龄是${value.age}`}
        {/* {value => <span>{value.username},年龄是{value.age}</span>} */}
    </Consumer>
    </h4>
  </div>
  )
}



相关文章
|
25天前
|
前端开发 JavaScript
React学习之——条件渲染
【10月更文挑战第16天】React 中没有像Vue中v-if这种指令。React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
|
2月前
|
前端开发 JavaScript
学习react基础(3)_setState、state、jsx、使用ref的几种形式
本文探讨了React中this.setState和this.state的区别,以及React的核心概念,包括核心库的使用、JSX语法、类与函数组件的区别、事件处理和ref的使用。
66 3
学习react基础(3)_setState、state、jsx、使用ref的几种形式
|
2月前
|
前端开发 JavaScript
react学习(13)props
react学习(13)props
|
2月前
|
前端开发
学习react基础(2)_props、state和style的使用
本文介绍了React中组件间数据传递的方式,包括props和state的使用,以及如何在React组件中使用style样式。
33 0
|
16天前
|
前端开发 JavaScript 安全
学习如何为 React 组件编写测试:
学习如何为 React 组件编写测试:
33 2
|
1月前
|
资源调度 前端开发 JavaScript
React进阶学习
React进阶学习
11 1
|
1月前
|
JSON 前端开发 JavaScript
React 进阶阶段学习计划
React 进阶阶段学习计划
|
2月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
28 2
|
2月前
|
前端开发
react学习(17)回调形式的ref
react学习(17)回调形式的ref
|
2月前
|
前端开发
react学习(15)函数式组件中使用props
react学习(15)函数式组件中使用props