likes
comments
collection
share

入门 React , JSX非常好用 🍉

作者站长头像
站长
· 阅读数 16

我正在参加「掘金·启航计划」

React是什么

一个专注于构建用户界面的 JavaScript 库,和vue和angular并称前端三大框架,不夸张的说,react引领了很多新思想,世界范围内是最流行的js前端框架

React英文文档(reactjs.org/

React中文文档 (zh-hans.reactjs.org/

React新文档 (beta.reactjs.org/

1.项目初始化

1.1脚手架创建项目 (不支持ts)

  1. 执行命令

    npx create-react-app react-basic  //react-basic 项目名称
    
    • npx create-react-app 是固定命令,create-react-app是React脚手架的名称
    • npx 命令会帮助我们临时安装create-react-app包,然后初始化项目完成之后会自自动删掉,所以不需要全局安装create-react-app
  2. 启动项目

        npm start
    

1.2 项目目录说明

  • 目录说明

    1. src 目录是我们写代码进行项目开发的目录
    2. package.json 中俩个核心库:react 、react-dom
  • 目录调整

    1. 删除src目录下自带的所有文件,只保留app.js根组件和index.js
    2. 创建index.js文件作为项目的入口文件,在这个文件中书写react代码即可
  • 入口文件说明

    import React from 'react'
    import ReactDOM from 'react-dom'
    import './index.css'
    // 引入根组件App
    import App from './App'
    // 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById('root')
    )
    

2. JSX 注意事项

  • JSX必须有一个根节点,如果没有根节点,可以使用<></>(幽灵节点)替代

  • 所有标签必须形成闭合,成对闭合或者自闭合都可以

  • JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 class -> className for -> htmlFor

  • JSX支持多行(换行),如果需要换行,需使用() 包裹,防止bug出现

3. JSX 基础

JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构

优势:

  1. 采用类似于HTML的语法,降低学习成本,会HTML就会JSX
  2. 充分利用JS自身的可编程能力创建HTML结构

注意:

  • JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法

    入门 React , JSX非常好用 🍉

3.1 JSX中使用js表达式

语法

{ JS 表达式 }

const name = '用户名'

<h1>你好,我叫{name}</h1>   //    <h1>你好,我叫用户名</h1>

可以使用的表达式

  1. 字符串、数值、布尔值、null、undefined、object( [] / {} )
  2. 1 + 2、'abc'.split('')、['a', 'b'].join('-')
  3. fn()

特别注意

if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!

3.2 JSX列表渲染

页面的构建离不开重复的列表结构,比如歌曲列表,商品列表等,我们知道 vue 中用的是 v-forreact 这边如何实现呢?

实现:使用数组的map 方法

// 来个列表
const songs = [
  { id: 1, name: '痴心绝对' },
  { id: 2, name: '像我这样的人' },
  { id: 3, name: '南山南' }
]

function App() {
  return (
    <div className="App">
      <ul>
        {
          songs.map(item => <li>{item.name}</li>)
        }
      </ul>
    </div>
  )
} 
export default App
  • 注意点:需要为遍历项添加 key 属性

    入门 React , JSX非常好用 🍉

  1. key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用
  2. key 在当前列表中要唯一的字符串或者数值(String/Number
  3. 如果列表中有像 id 这种的唯一值,就用 id 来作为 key
  4. 如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key

3.3 JSX条件渲染

作用:根据是否满足条件生成HTML结构,比如Loading效果

实现:可以使用 三元运算符逻辑与(&&)运算符

// 来个布尔值
const flag = true

// 函数 
const loadData=()=>{
  if (isLoading) {
    return loadingUI;
  } else {
    return successUI;
  }
}
function App() {
  return (
    <div className="App">
      {/* 条件渲染字符串 */}
      {flag ? 'react真有趣' : 'vue真有趣'}
      {/* 条件渲染标签/组件 */}
      {flag ? <span>this is span</span> : null}
      <div>{loadData()}</div>;
    </div>
  )
}
export default App

3.4 jsx样式处理

  1. 行内样式 style

    function App() {
      return (
        <div className="App">
          <div style={{ color: 'red' }}>this is a div</div>
        </div>
      )
    }
    
    export default App 
    
  2. 行内样式- style- 更优写法

    
     //定义常量
    const styleObj = {
        color:red
    }
    
    function App() {
      return (
        <div className="App">
          <div style={ styleObj }>this is a div</div>
        </div>
      )
    }
    
    export default App
    
  3. 类名 - className(推荐)

    app.css

    .title {
      font-size: 30px;
      color: blue;
    }
    

    app.js

    import './app.css'
    
    function App() {
      return (
        <div className="App">
          <div className='title'>this is a div</div>
        </div>
      )
    }
    export default App
    
  4. 类名- className- 动态类名控制

    import './app.css'
    const showTitle = true
    function App() {
      return (
        <div className="App">
          <div className={ showTitle ? 'title' : ''}>this is a div</div>
        </div>
      )
    }
    export default App
    

4. 组件

使用JS的函数(或箭头函数 ) 创建的组件,就叫做函数组件

4.1 函数组件

  • 组件定义与渲染

    // 定义函数组件
    function HelloFn () {
      return <div>这是我的第一个函数组件!</div>
    }
    
    // 定义类组件
    function App () {
      return (
        <div className="App">
          {/* 渲染函数组件 */}
          <HelloFn />
          <HelloFn></HelloFn>
        </div>
      )
    }
    export default App
    
  • 约定说明

    1. 组件的名称必须 首字母大写,react内部会根据这个来判断是组件还是普通的HTML标签
    2. 函数组件必须有函数返回值 ,表示该组件 UI 结构 ;如果不需要渲染任何内容,则返回 null
    3. 组件渲染的内容是函数的返回值对应的内容

4.2 类 组件

使用ES6的class创建的组件,叫做类 (class) 组件

  • 类组件定义及渲染

    // 引入React
    import React from 'react'
    
    // 定义类组件
    class HelloC extends React.Component {
      render () {
        return <div>这是我的第一个类组件!</div>
      }
    }
    
    function App () {
      return (
        <div className="App">
          {/* 渲染类组件 */}
          <HelloC />
          <HelloC></HelloC>
        </div>
      )
    }
    export default App
    
  • 约定说明

    1. 类名称也必须以大写字母开头
    2. 类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性
    3. 类组件必须提供 render 方法render 方法必须有返回值,表示该组件的 UI 结构

4.3 组件状态

步骤:初始化状态=> 读取状态=>修改状态=>影响视图

初始化状态

  • 通过class 的实例属性state来初始化

  • state 的值是一个对象结构,表示组件可以有多个数据状态

    class Counter extends React.Component {
      // 初始化状态
      state = {
        count: 0
      }
      render() {
        return <button>计数器</button>
      }
    }
    

读取状态

  • 通过 this.state 获取状态

    class Counter extends React.Component {
      // 初始化状态
      state = {
        count: 0
      }
      render() {
        // 读取状态
        return <button>计数器{this.state.count}</button>
      }
    }
    

修改状态

  • 语法

    this.setState({要修改的部分数据})

  • setState 方法作用

    1. 修改state中的数据 状态
    2. 更新视图
  • 思想:数据驱动视图,也就是只要修改数据状态,那么页面就会自动刷新,无需手动操作Dom

  • 注意事项:不要直接修改state 中的值 ,必须通过setState 方法进行修改

    class Counter extends React.Component {
      // 定义数据
      state = {
        count: 0
      }
      // 定义修改数据的方法
      setCount = () => {
        this.setState({
          count: this.state.count + 1
        })
      }
      // 使用数据 并绑定事件
      render () {
        return <button onClick={this.setCount}>{this.state.count}</button>
      }
    }
    

    React 中 state 设计的背后原理(原因):函数式编程中的数据不可变思想,简单来说就是变量一旦创建不可修改,所谓修改就是重新创建数据。

    一些参考文章:

    www.imgeek.org/article/825…

    www.kancloud.cn/chandler/re…

    www.jianshu.com/p/89f1d4245…

    www.zhihu.com/question/53…

5 事件

5.1 如何绑定事件

  • 语法

    on + 事件名称 = { 事件处理程序 } ,比如:<div onClick={()=>{}}></div>

  • 注意点

    react 事件采用驼峰命名法,比如:onMouseEnter、onFocus

  • 样例

    // 函数组件
    function HelloFn () {
      // 定义事件回调函数
      const clickHandler = () => {
        console.log('事件被触发了')
      }
      return (
        // 绑定事件
        <button onClick={clickHandler}>click me!</button>
      )
    }
    
    // 类组件
    class HelloC extends React.Component {
      // 定义事件回调函数
      clickHandler = () => {
        console.log('事件被触发了')
      }
      render () {
        return (
          // 绑定事件
          <button onClick={this.clickHandler}>click me!</button>
        )
      }
    }
    

5.2 获取事件对象

  • 通过事件处理程序的参数获取事件对象e

    // 函数组件
    function HelloFn () {
      // 定义事件回调函数
      const clickHandler = (e) => {
        e.preventDefault()
        console.log('事件被触发了', e)
      }
      return (
        // 绑定事件
        <a href="http://itcast.cn/" onClick={clickHandler}>事件</a>
      )
    }
    

6. this问题说明

入门 React , JSX非常好用 🍉

// class fields写法 推荐!!
class Counter extends React.Component {
  // 定义数据
  state = {
    count: 0
  }
  // 定义修改数据的方法
  setCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  // 使用数据 并绑定事件
  render () {
    return <button onClick={this.setCount}>{this.state.count}</button>
  }
}

// 箭头函数写法
class Counter extends React.Component {
  // 定义数据
  state = {
    count: 0
  }
  // 定义修改数据的方法
  setCount () {
    this.setState({
      count: this.state.count + 1
    })
  }
  // 使用数据 并绑定事件
  render () {
    return <button onClick={() => this.setCount()}>{this.state.count}</button>
  }
}

// constructor中通过bind强行绑定this
class Counter extends React.Component {
  // 定义数据
  state = {
    count: 0
  }
  constructor() {
    super()
    this.setCount = this.setCount.bind(this)
  }
  // 定义修改数据的方法
  setCount () {
    this.setState({
      count: this.state.count + 1
    })
  }
  // 使用数据 并绑定事件
  render () {
    return <button onClick={this.setCount}>{this.state.count}</button>
  }
}

总结

onClick={ ( ) => this.handleClick( ) } 箭头函数中 handleClick() 为什么加括号

效果是延迟调用,目的是在指定的onClick函数执行之前可以做进一步的额外处理,可以让开发者在拿到事件处理对象后做其他业务逻辑处理,业务逻辑处理结束后,交给返回的函数做下一步处理,这个函数可以接受新的参数

7.表单处理

目标任务: 能够使用受控组件的方式获取文本框的值

使用React处理表单元素,一般有俩种方式:

  1. 受控组件 (推荐使用)
  2. 非受控组件 (了解)

7.1 受控表单组件

什么是受控组件?

input框自己的状态被React组件状态控制 React组件的状态的地方是在 state中,input 表单元素也有自己的状态是在value中,React 将 state 与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证单一数据源特性

实现步骤

以获取文本框的值为例,受控组件的使用步骤如下:

  1. 在组件的state中声明一个组件的状态数据
  2. 将状态数据设置为input标签元素的vaule属性的值
  3. input添加change事件,在事件处理程序中,通过事件对象 e.target.value 获取到当前文本框的值( 当前最新的输入的值)
  4. 调用setState方法, 将文本框的值作为state 状态的最新值

代码落地

import React from 'react'

class InputComponent extends React.Component {
  // 声明组件状态
  state = {
    message: 'this is message',
  }
  // 声明事件回调函数
  changeHandler = (e) => {
    this.setState({ message: e.target.value })
  }
  render () {
    return (
      <div>
        {/* 绑定value 绑定事件*/}
        <input value={this.state.message} onChange={this.changeHandler} />
      </div>
    )
  }
}
function App () {
  return (
    <div className="App">
      <InputComponent />
    </div>
  )
}
export default App

7.2 非受控表单组件

什么是非受控组件?

非受控组件就是通过手动操作 dom 的方式获取文本框的值,文本框的状态不受 react 组件的 state 中的状态控制,直接通过原生dom 获取输入框的值

实现步骤

  1. 导入createRef 函数
  2. 调用 createRef 函数,创建一个 ref 对象,存储到名为msgRef的实例属性中
  3. 为input添加 ref 属性,值为msgRef
  4. 在按钮的事件处理程序中,通过msgRef.current即可拿到 input 对应的 dom 元素,而其中msgRef.current.value拿到的就是文本框的值

代码落地

import React, { createRef } from 'react'

class InputComponent extends React.Component {
  // 使用createRef产生一个存放dom的对象容器
  msgRef = createRef()

  changeHandler = () => {
    console.log(this.msgRef.current.value)
  }

  render() {
    return (
      <div>
        {/* ref绑定 获取真实dom */}
        <input ref={this.msgRef} />
        <button onClick={this.changeHandler}>click</button>
      </div>
    )
  }
}

function App () {
  return (
    <div className="App">
      <InputComponent />
    </div>
  )
}
export default App

8 组件通信

  1. 父子关系
  2. 兄弟关系--自定义事件模式产生技术方法eventBus/通过共同的父组件通信
  3. 其它关系--mobx/redux /基于hook的方案

8.1 父传子实现

  1. 父组件提供要传递的数据 -- state

  2. 给子组件标签添加属性值 为 state 中的数据

    入门 React , JSX非常好用 🍉

props

  1. props是只读对象(readonly)

    • 根据单项数据流的要求,子组件只能读取 props 中的数据
  2. props 可以传递任意数据

    • 数字、字符串、布尔值 ,数组,对象,函数 ,jsx
  3. 子组件中通过 props 接收 父组件中传递的数据

    • 类组件使用 this.props 获取 props 对象
    • 函数式组件直接通过参数获取 props 对象
import React from 'react'

// 函数式子组件
function FSon(props) {
  console.log(props)
  return (
    <div>
      子组件1
      {props.msg}
    </div>
  )
}

// 类子组件
class CSon extends React.Component {
  render() {
    return (
      <div>
        子组件2
        {this.props.msg}
      </div>
    )
  }
}

class App extends React.Component {
  state = {
    message: 'this is message'
  }
  render() {
    return (
      <div>
        <div>父组件</div>
        <FSon 
          msg={this.state.message} 
          age={20} 
          isMan={true} 
          cb={() => { console.log(1) }} 
          child={<span>this is child</span>}
        />
        <CSon msg={this.state.message} />
      </div>
    )
  }
}

入门 React , JSX非常好用 🍉

8.2 子传父实现

口诀:父组件 给 子组件传递 回调函数,子组件调用

实现步骤

  1. 父组件提供一个回调-->用于接收数据

  2. 将函数作为属性的值,传给子组件

  3. 子组件通过 props 调用回调函数

  4. 将子组件中的数据 作为参数传递给回调函数

    入门 React , JSX非常好用 🍉

代码实现

import React from 'react'

// 子组件
function Son(props) {
  function handleClick() {
    // 调用父组件传递过来的回调函数 并注入参数
    props.changeMsg('this is newMessage')
  }
  return (
    <div>
      {props.msg}
      <button onClick={handleClick}>change</button>
    </div>
  )
}


class App extends React.Component {
  state = {
    message: 'this is message'
  }
  // 提供回调函数
  changeMessage = (newMsg) => {
    console.log('子组件传过来的数据:',newMsg)
    this.setState({
      message: newMsg
    })
  }
  render() {
    return (
      <div>
        <div>父组件</div>
        <Son
          msg={this.state.message}
          // 传递给子组件
          changeMsg={this.changeMessage}
        />
      </div>
    )
  }
}

export default App

8.3 兄弟组件通信

核心思路: 通过状态提升机制,利用共同的父组件实现兄弟通信

入门 React , JSX非常好用 🍉

实现步骤

  1. 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

    • 提供共享状态
    • 提供操作共享状态的方法
  2. 要接收数据状态的子组件通过 props 接收数据

  3. 要传递数据状态的子组件通过 props 接收方法,调用方法传递数据

代码实现

import React from 'react'

// 子组件A
function SonA(props) {
  return (
    <div>
      SonA
      {props.msg}
    </div>
  )
}
// 子组件B
function SonB(props) {
  return (
    <div>
      SonB
      <button onClick={() => props.changeMsg('new message')}>changeMsg</button>
    </div>
  )
}

// 父组件
class App extends React.Component {
  // 父组件提供状态数据
  state = {
    message: 'this is message'
  }
  // 父组件提供修改数据的方法
  changeMsg = (newMsg) => {
    this.setState({
      message: newMsg
    })
  }

  render() {
    return (
      <>
        {/* 接收数据的组件 */}
        <SonA msg={this.state.message} />
        {/* 修改数据的组件 */}
        <SonB changeMsg={this.changeMsg} />
      </>
    )
  }
}

export default App

8.4 跨组件通信 Context

上图是一个react形成的嵌套组件树,如果我们想从App 组件向任意一下层组件传递数据,可以使用 Context 向组件树间进行数据传递的方法

入门 React , JSX非常好用 🍉

实现步骤

  1. 创建Context 对象 导出 ProviderConsumer对象

    const { Provider, Consumer } = createContext()
    
  2. 使用 Provider 包裹根组件提供数据

    <Provider value={this.state.message}>
        {/* 根组件 */}
    </Provider>
    
  3. 需要用到数据的组件使用 Consumer 包裹获取数据

    <Consumer >
        {value => /* 基于 context 值进行渲染*/}  
    </Consumer>
    

代码实现

import React, { createContext }  from 'react'

// 1. 创建Context对象 
const { Provider, Consumer } = createContext()


// 3. 消费数据
function ComC() {
  return (
    <Consumer >
      {value => <div>{value}</div>}
    </Consumer>
  )
}

function ComA() {
  return (
    <ComC/>
  )
}

// 2. 提供数据
class App extends React.Component {
  state = {
    message: 'this is message'
  }
  render() {
    return (
      <Provider value={this.state.message}>
        <div className="app">
          <ComA />
        </div>
      </Provider>
    )
  }
}

export default App

9. React 组件进阶

9.1 children 属性

children属性是什么

表示该组件的子节点,只要组件内部有子节点,props中就有该属性

children可以是什么

  1. 普通文本
  2. 普通标签元素
  3. 函数
  4. JSX

目的:高阶组件 render Props

9.2 props 校验-场景和使用

对于组件来说,props是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键 - 组件的使用者可能报错了也不知道为什么,看下面的例子

const List=props=>{
 const arr= props.colors
 const lis=arr.map((item,index)=><li key={index}>{item,name}</li>)
   return(
    <ul>{lis}</ul> 
   )
}

<List colors={19}/>

面对这样的问题,如何解决? props校验

实现步骤

  1. 安装属性校验包:yarn add prop-types
  2. 导入prop-types
  3. 使用 组件名.propTypes = {} 给组件添加校验规则

核心代码

//里面有各种各样的内置的校验规则
import PropTypes from 'prop-types'

const List = props => {
  const arr = props.colors
  const lis = arr.map((item, index) => <li key={index}>{item.name}</li>)
  return <ul>{lis}</ul>
}

//组件名.propTypes 小写驼峰
List.propTypes = {
  //定义各种规则
  colors: PropTypes.array
}

9.3 props校验-规则说明

四种常见结构

  1. 常见类型:arrayboolfuncnumberobjectstring
  2. React元素类型:element
  3. 必填项:isRequired
  4. 特定的结构对象:shape({})

核心代码

// 常见类型
optionalFunc: PropTypes.func,
// 必填 只需要在类型后面串联一个isRequired
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
  color: PropTypes.string,
  fontSize: PropTypes.number
})

官网文档更多阅读:reactjs.org/docs/typech…

9.4 props 校验-默认值

通过 defaultProps 可以给组件的props设置默认值,在未传入props的时候生效

1.函数组件

  • 直接使用函数参数默认值 (推荐)

        function List({pageSize = 10}) {
          return (
            <div>
              此处展示props的默认值:{ pageSize }
            </div>
          )
        }
    
        // 不传入pageSize属性
        <List />
    

2.类组件

  • 使用类静态属性声明默认值,static defaultProps = {}

    class List extends Component {
      static defaultProps = {
        pageSize: 10
      }
    
      render() {
        return (
          <div>
            此处展示props的默认值:{this.props.pageSize}
          </div>
        )
      }
    }
    <List />
    

10 生命周期 -概述

组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期(类组件 实例化 函数组件 不需要实例化)

projects.wojtekmaj.pl/react-lifec…

入门 React , JSX非常好用 🍉

10.1 生命周期 - 挂载阶段

入门 React , JSX非常好用 🍉

钩子 函数触发时机作用
constructor创建组件时,最先执行,初始化的时候只执行一次1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等
render每次组件渲染都会触发渲染UI(注意: 不能在里面调用setState()
componentDidMount组件挂载(完成DOM渲染)后执行,初始化的时候执行一次1. 发送网络请求 2.DOM操作

10.2 生命周期 - 更新阶段

入门 React , JSX非常好用 🍉

钩子函数触发时机作用
render每次组件渲染都会触发渲染UI(与 挂载阶段 是同一个render)
componentDidUpdate组件更新后(DOM渲染完毕)DOM操作,可以获取到更新后的DOM内容,不要直接调用setState

10.3 生命周期 - 卸载阶段

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

11 Hooks 基础

Hooks 的本质:一套能够使函数组件更强大,更灵活的钩子

React 体系里组件分为 类组件 和 函数组件

注意点:

  1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
  2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
  3. hooks只能在函数组件中使用

11.1 Hooks解决了什么问题

Hooks的出现 解决了俩个问题:

  1. 组件的逻辑复用

    在hooks出现之前,react先后尝试了 mixins 混入,HOC高阶组件,render-props等模式但是都有各自的问题,比如 mixin的数据来源不清晰,高阶组件的嵌套问题等等

  2. class组件自身的问题

    class组件就像一个厚重的 "战船" 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this 指向问题等等,而我们更多时候需要的是一个轻快灵活的快艇

11.2 useState

作用[count, setCount]

useState 为函数组件提供状态(state)

使用步骤

  1. 导入 useState`函数
  2. 调用 useState函数,并传入状态的初始值
  3. useState 函数的返回值中,拿到现状态和修改状态的方法
  4. 在 JSX中展示状态
  5. 调用修改状态的方法更新状态

代码实现

import { useState } from 'react'

function App() {
  // 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
  // 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}
export default App

注意:

[count, setCount] -> 名字可以自定义(保持语义化)

可以换吗 -> 不可以 第一个参数就是数据状态,第二个参数就是修改数据的方法

1.状态的读取和修改

读取状态

该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用

修改状态

  1. setCount是一个函数 ,调用该函数后,将使用新值替换旧值
  2. 修改状态后,由于状态发生变化,会引起视图发生变化

2.组件的更新过程

函数组件使用 useState hook 后的执行过程,以及状态值 的变化

  • 组件第一次渲染

    1. 从头开始执行该组件中的代码逻辑
    2. 调用 useState(0) 将传入的参数作为状态初始值,即:0
    3. 渲染组件,此时,获取到的状态 count 值为: 0
  • 组件第二次渲染

    1. 点击按钮,调用 setCount(count +1 )修改 状态,因为状态发生改变,所以该组件会重新渲染
    2. 组件重新渲染时,会再次执行该组件中的代码逻辑
    3. 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
      1. 再次渲染组件,此时,获取到的状态 count 值为:1

    注意:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值

import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  // 在这里可以进行打印测试
  console.log(count)
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}
export default App

3 使用规则

  • useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态

    function List(){
      // 以字符串为初始值
      const [name, setName] = useState('cp')
      // 以数组为初始值
      const [list,setList] = useState([])
    }
    
  • useState 注意事项

    1. 只能出现在函数组件中

    2. 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)

      let num = 1
      function List(){
        num++
        if(num / 2 === 0){
           const [name, setName] = useState('cp') 
        }
        const [list,setList] = useState([])
      }
      // 俩个hook的顺序不是固定的,这是不可以的!!!
      
    3. 可以通过开发者工具查看 hooks 状态

11.3 useEffect

1. 函数的副作用

对于React 组件来说,主作用就是根据数据 (state/props) 渲染Ul ,除此之外都是副作用(比如,手动修改Dom)

常见的副作用

  1. 数据请求ajax 发送
  2. 手动修改Dom
  3. localstorage 操作

useEffect函数的作用就是为 react 函数组件提供副作用处理的!

2. 基础使用

为 react 函数组件提供副作用处理

使用步骤

  1. 导入 useEffect 函数
  2. 调用 useEffect 函数,并传入回调函数
  3. 在回调函数 中编辑写副作用处理 (dom操作)
  4. 修改数据状态
  5. 检测副作用是否生效

代码实现

import { useEffect, useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
 
  useEffect(()=>{
    // dom操作
    document.title = `当前已点击了${count}次`
  })
  return (
    <button onClick={() => { setCount(count + 1) }}>{count}</button>
  )
}

export default App

3. 依赖项控制执行时机

  1. 不添加依赖项

    组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行

    • 组件初始渲染
    • 组件更新(不管是哪个状态引起的更新)
    useEffect(()=>{
       console.log("副作用执行了")
    })
    
  2. 添加空数组

    组件只在首次渲染时执行一次

    useEffect(()=>{
       console.log('副作用执行了')
    },[])
    
  3. 添加特定依赖项

    • 副作用函数在首次渲染时执行
    • 在依赖项发生变化时重新执行
    function App() {  
        const [count, setCount] = useState(0)  
        const [name, setName] = useState('zs') 
        
        useEffect(() => {    
            console.log('副作用执行了')  
        }, [count])  
        
        return (    
            <>      
             <button onClick={() => { setCount(count + 1) }}>{count}</button>      
             <button onClick={() => { setName('cp') }}>{name}</button>    
            </>  
        )
    }
    

注意事项

useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现

某种意义 hook 的出现,就是想不用生命周期概念也可以写业务代码

12. Hooks进阶

12.1 useState - 回调函数的参数

使用场景

参数只会在组件初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被 调用

语法

const [name, setName] = useState(()=>{    // 编写计算逻辑    return '计算之后的初始值'})

语法规则

  1. 回调函数 return 出去的值 将作为 name 的初始值
  2. 回调函数中的逻辑只会在组件初始化的时候执行一次

语法选择

  1. 如果就是初始化一个普通的数据 直接使用 useState(普通数据)即可
  2. 如果要初始化的数据 无法直接得到需要通过计算才能获得,使用 useState(()=>{})

来个需求

入门 React , JSX非常好用 🍉

12.2 useEffect - 清理副作用

使用场景

在组件被销毁时,如果有些副作用操作需要被清理,就可以使用 useEffect - 清理副作用 比如常见的定时器

语法及规则

import { useEffect, useState } from 'react'

function Foo() {  
    useEffect(() => {   
        const timerId = setInterval(() => {      
            console.log('副作用函数执行了')    
        }, 1000)   
        // 添加清理副租用函数   
        // 副作用函数的执行时机为: 在下一次副作用函数执行之前执行  
        return () => {
            clearInterval(timerId)    
        }  
    })  
    return <div>Foo</div>
}

12.3 useEffect - 发送网络请求

使用场景

如何在 useEffect 中改善网络请求,并且封装同步 async await操作

语法要求

不可以直接在 useEffect 的回调函数外层直接包裹 await 因为异步会导致清理函数无法立即返回

  • 错误写法

    useEffect(async ()=>{    
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')   
        console.log(res)
    },[])
    
  • 正确写法

    在内部单独定义一个函数,然后把这个函数包装成同步

    useEffect(()=>{   
        async function fetchData(){      
           const res = await axios.get('http://geek.itheima.net/v1_0/channels')    
           
           console.log(res)   
        } 
    },[])
    

12.4 useRef

使用场景

在函数组件中获取真实的Dom元素对象或者是组件对象

使用步骤

  1. 导入 useRef 函数
  2. 执行 useRef 函数并传入null,返回值为一个对象内部有一个current属性存放拿到的 dom 对象(组件实例)
  3. 通过 ref 绑定要获取的元素或组件

获取Dom

useEffect 回调,是在 dom 渲染之后 还是在之前 ==>之后

useEffect 和 vue 中的 wacth 效果比较像,但是执行时机是不同的

import { useEffect, useRef } from 'react'
function App() {  
    const h1Ref = useRef(null)  
    useEffect(() => {    
        console.log(h1Ref)  
    },[])  
    return (    
        <div>      
            <h1 ref={ h1Ref }>this is h1</h1>    
        </div>  
    )
}
export default App

获取组件实例

函数组件由于没有实例,不能使用 ref 获取,如果想获取组件实例,必须是类组件

Foo.js

class Foo extends React.Component {  
    sayHi = () => {    
        console.log('say hi')  
    }  
    render(){    
        return <div>Foo</div>  
    }
}
    
export default Foo

App.js

import { useEffect, useRef } from 'react'
import Foo from './Foo'
function App() {  
    const h1Foo = useRef(null)  
    useEffect(() => {    
        console.log(h1Foo)  
    }, [])  
    return (    
        <div> <Foo ref={ h1Foo } /></div>  
    )
}
export default App

12.5 useContext

实现步骤

  1. 使用createContext 创建Context对象
  2. 在顶层组件通过Provider 提供数据
  3. 在底层组件通过useContext函数获取数据

代码实现

Context 如果要传递的数据,只需要在整个应用初始化时候传递一次,就可以选择当前文件里做数据提供

如果 Context 需要传递数据并且将来还需要在对数据做修改,底层组件也需要跟着一起变,写到 app.js

import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()

function Foo() {  
    return <div>Foo <Bar/></div>
}

function Bar() {  
    // 底层组件通过useContext函数获取数据  
    const name = useContext(Context)  
    return <div>Bar {name}</div>
}

function App() {  
    return (    
        // 顶层组件通过Provider 提供数据    
        <Context.Provider value={'this is name'}>     
            <div><Foo/></div>    
        </Context.Provider>  
    )
}

export default App