React框架基础 – 1、基础理论

笔记来源:拉勾教育 – 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

React框架基础

体验React

官网地址中文文档

  • React是facebook推出的用于构建用户界面的前端js框架
  • React使用组件构建用户页面

组件开发的优势

  • 将一个庞大、复杂的应用拆分成多个独立单元
  • 组件之间相互独立,有利于应用程序的维护
  • 组件可以复用,一次编写多地复用

注意:React项目中必须要依赖于react-dom和react-scripts两个包

自定义webpack搭建React环境

  1. 快速构建一个项目:npm init --yes

  2. 书写开发依赖:在package.json中添加书写依赖

    "devDependencies": {
        "@babel/core": "^7.13.8",
        "@babel/preset-env": "^7.13.9",
        // 转换 react语法专用的插件
        "@babel/preset-react": "^7.12.13",
        // babel-loader
        "babel-loader": "^8.2.2",
        // 限制为 4.xx ,新的 5.xx webpxck里面对应的 html-webpack-plugin 也是 5.xx版本
        "html-webpack-plugin": "^4.5.2",
        // 下面两个是react核心,必须安装
        "react": "^17.0.1",
        "react-dom": "^17.0.1"
        // webpack 和 webpack-cli 没有用最新的 5.xx版本
        "webpack": "^4.46.0",
        "webpack-cli": "^3.3.12",
        // 在线运行开发服务器
        "webpack-dev-server": "^3.11.2"
      }
    复制代码
  3. 安装依赖:npm i

  4. 书写 webpack 配置文件 – package.config.js

    // 引入path内置对象
    const path = require('path')
    // 引入webpack - html插件
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    // webpack配置对象
    module.exports = {
      // 模式 - 开发模式
      mode: 'development',
      // devtool不设置
      devtool: 'none',
      // 入口文件
      entry: './src/index.js',
      // 输出
      output: {
        // 输出文件名
        filename: 'main.js',
        // 输出目录,使用path对象方法
        path: path.resolve('dist')
      },
      // 设置本地服务器
      devServer: {
        // 端口
        port: 3000,
        // 热更新
        hot: true
      },
      module: {
        rules: [
          {
            // js 和 jsx 文件的加载器
            test: /\.js|jsx$/,
            // 去除 node_modules 目录
            exclude: /node_modules/,
            use: [
              {
                // 使用 babel
                loader: 'babel-loader',
                // 使用的包
                options: {
                  presets: ['@babel/preset-env', '@babel/preset-react']
                }
              }
            ]
          }
        ]
      },
      // 插件
      plugins: [
        // 创建html插件实例
        new HtmlWebpackPlugin({
          // 模版为 index.html 文件
          template: './src/index.html'
        })
      ]
    }
    复制代码
  5. 新建 src 目录,创建 webpack 入口文件 – index.js 和 Html 模版 – index.html

  6. 在html模版中快速创建结构并且添加一个 id 为 root 的元素

    <body>
      <!-- 作为React渲染的根元素 -->
      <div id="root"></div>
    </body>
    复制代码
  7. 书写入口文件index.js

    // 引入react,注意这里引入后要使用首字母大写的 React
    import React from 'react'
    // 引入react-dom 中的 render 方法
    import { render } from 'react-dom'
    
    // 创建函数模版
    function App() {
      // 返回结构
      return <div>Hello React</div>
    }
    
    // 使用render方法进行渲染
    // 参数1:渲染的模版
    // 参数2:渲染根元素
    render(<App />, document.getElementById('root'))
    复制代码
  8. 运行webpack-dev-server查看项目:npx webpack-dev-server

结论:React本质上也需要使用webpack进行打包处理,即使后续我们使用脚手架创建react项目,其底层也集成了webpack

脚手架创建项目并初始化

  1. 全局安装脚手架工具:(sudo)npm install -g create-react-app

  2. 创建项目: create-react-app 项目名,程序会自动帮助我们创建项目并进行相关设置形成一个新的React项目

  3. 初始化项目:删除我们不需要的文件

    • public目录下只留下 index.html 文件
    • src目录下只留下 app.js 和 index.js 文件
    • 其他项目无需修改
  4. 修改剩文件中的import、src和内容

    public/index.html中删除多余的引入文件和注释,只留下下面内容
    
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta
          name="description"
          content="Web site created using create-react-app"
        />
        <title>React App</title>
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>
    复制代码
    // src/App.js中删除多余的inport,简化app模版内容,只留下下面内容
    
    function App() {
      return (
        <div>Hello React</div>
      )
    }
    
    export default App
    复制代码
    src/index.js中删除多余的inport,只留下下面内容
    
    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      document.getElementById('root')
    )
    复制代码
  5. 运行项目:npm / yarn start,老师使用的是npm,react推荐的是yarn

JSX语法

JSX可以看作是js语言的扩展,它既不是一段字符串,也不是一个HTML

JSX具备js的所有功能,同时可以被转为HTML在界面上进行展示(react、react-dom)

JSX的一些功能

  • 动态显示数据,使用{ }
  • 调用方法
  • 支持表达式,不支持if语句(支持三元表达式)
  • 支持模版字符串
  • 注意:不能直接使用对象
// 创建实验用的变量
const name = '拉勾教育'
function sayHi() {
  return 'Hi 拉勾教育'
}
const a = false
const obj  = {
  name: '拉勾教育',
  age: 100
}

function App() {
  return (
    // 注意:和Vue一样只能有一个容器标签
    <div>
      {/* 直接调用变量无效 */}
      <p>name</p>
      {/* 想使用变量必须在{}中使用 */}
      <p>{name}</p>
      {/* 使用函数 */}
      <p>{sayHi()}</p>
      {/* 使用三元表达式 */}
      <p>{a ? 1 : 2}</p>
      {/* 使用模版字符串 */}
      <p>{`hello ${name}`}</p>
      {/* 调用js内置方法 */}
      <p>{console.log('打印')}</p>
      {/* 对象需要转字符串之后词啊能使用 */}
      <p>{JSON.stringify(obj)}</p>
      {/* 写注释 */}
      <p>{/* 注释要这么写 */}</p>
    </div>
  )
}

export default App

复制代码

上面函数最终return使用的就是JSX,可以直接当做HTML使用

JSX特点

  • JSX本身就是一个表达式,所以可以作为变量赋值、函数参数 / 返回值等等
  • JSX可以添加属性(使用双引号包裹)、动态属性
  • JSX可以添加子元素
  • JSX(return返回)只能存在一个顶层元素(类似Vue组件)
  • 使用但标签必须书写关闭
// JSX作为表达式const a = <p>作为表达式</p>const b = 123function App() {  return (    // jsx作为了函数的返回值    <div>      {a}      <p title="添加属性">添加属性</p>      <p value={b}>添加动态属性</p>      <img />    </div>  )}export default App
复制代码

建议return后面使用()返回js,便于代码管理,不加也没有什么问题

JSX事件处理

  • 事件绑定

    • 直接使用驼峰命名法在元素生添加属性即可

      const clickBtn = () => {  console.log('事件执行了')}function App() {  return (    <div>      <button onClick={clickBtn}>点击触发事件</button>    </div>  )}
      复制代码
  • 事件传参

    • 利用箭头函数,在箭头函数内调用传参即可

    • 使用bind返回一个新的函数,在新的函数内传参

      const clickBtn = (a, b) => {  console.log(a, b)  console.log('事件执行了')}function App() {  return (    <div>      {/* 使用箭头函数 */}      <button onClick={() => {clickBtn(1, 2)}}>点击触发事件</button>      {/* 使用bind */}      <button onClick={clickBtn.bind(null, 1, 2)}>点击触发事件</button>    </div>  )}
      复制代码
  • 获取事件对象

    • 默认情况下不接收参数,默认第一个形参就是事件对象本身

    • 使用箭头函数传参,在箭头函数传递参数,即为事件对象本身

    • 使用bind传递参数,最后一个参数即为事件对象本身

    • 使用bind不传参和第一种一样

      // 三个事件函数const clickBtn = (e) => {  console.log(e)  console.log('事件1执行了')}const clickBtn2 = (a, b, e) => {  console.log(e)  console.log('事件2执行了')}const clickBtn3 = (a, b, e) => {  console.log(e)  console.log('事件3执行了')}function App() {  return (    <div>      {/* 默认不传参,第一个参数就是事件对象本身 */}      <button onClick={clickBtn}>点击触发事件</button>      {/* 使用箭头函数传参,事件对象在箭头函数参数中获取 */}      <button onClick={(e) => {clickBtn2(1, 2, e)}}>点击触发事件2</button>      {/* 使用bind,事件参数为形参的最后一个 */}      <button onClick={clickBtn3.bind(null, 1, 2)}>点击触发事件3</button>    </div>  )}
      复制代码

循环数据遍历

JSX一个特点:直接使用数组会自动解构并且遍历数组内容

const arr = [<p>1</p>, <p>2</p>, <p>3</p>]function App() {  return (    <div>      // jsx会将数组中的每一项解构出来,然后使用,因为我这里每一项都是标签,所以渲染出来直接就是三个p标签      {arr}    </div>  )}
复制代码

利用这个特点进行复杂数据的遍历

// 创建一个数组,每一项都是jsx不能处理的对象结构const arr = [  {    id: 0,    name: 'zs',    age: 18,    sex: 0  },  {    id: 1,    name: 'hmm',    age: 20,    sex: 1  }]function App() {  // 利用map方法对数组进行遍历,将jsx返回li组合成新数组  const newArr = arr.map(item => {    return (      // 这里需要使用key做标记,与Vue同理      <li key={item.id}>        <p>{item.name}</p>        <p>{item.age}</p>        <p>{item.sex}</p>      </li>    )  })  return (    <div>      {/* 使用新的数组,jsx会自动解构 */}      <ul>{newArr}</ul>    </div>  )}
复制代码

添加内联 / 行内样式

添加内联样式支持4种方式

  • 直接添加内联样式

    function App() {  return (    <div>      {/* 直接使用内联样式进行添加          单独写在一个{}内部,不需要写单位、使用驼峰命名法、      */}      <p style={{width: 100, height: 100, backgroundColor: 'pink'}}></p>    </div>  )}
    复制代码
  • 将内联样式单独封装为一个对象引入

    	// 样式对象const style = {  width: 100,  height: 100,  backgroundColor: 'pink'}function App() {  return (    <div>      {/* 单独封装样式对象直接引用即可 */}      <p style={style}></p>    </div>  )}export default App
    复制代码
  • 默认不支持伪类和媒体查询:需要使用radium包实现支持

    安装依赖:npm install --save radium

    src/App.js// 引入radiumimport Radium from 'radium'// 样式对象const style = {  width: 100,  height: 100,  backgroundColor: 'pink',  // 伪类 - 鼠标以上  ":hover": {backgroundColor: 'skyblue'},  // 媒体查询,视窗宽度小于500宽度为200px(媒体查询还需要再使用app的组件中进行设置一下)  "@media (max-width: 500px)": {width: 200}}function App() {  return (    <div>      {/* 单独封装样式对象直接引用即可 */}      <p style={style}></p>    </div>  )}// 使用radium包裹住再进行导出就可以支持了export default Radium(App)
    复制代码
    // src/index.js  再index.js入口文件中使用radiumimport React from 'react'import ReactDOM from 'react-dom'import App from './App'// 引入radium中的StyleRoot方法import {StyleRoot} from 'radium'ReactDOM.render(  <React.StrictMode>    {/* 使用StyleRoot包裹app就可以使用媒体查询了 */}    <StyleRoot><App /></StyleRoot>  </React.StrictMode>,  document.getElementById('root')
    复制代码
  • radium提供多样式添加

    // 引入radiumimport Radium from 'radium'const button = {  // 内部书写多个样式  base: {    width: 100,    height: 100,    backgroundColor: '#fff'  },  login: {    backgroundColor: 'green'  },  logout: {    backgroundColor: 'red'  }}// 信号值const login = falsefunction App() {  return (    <div>      {/* style内联样式月可以书写成数组形式 */}      <button style={[        // 第一个样式为公共样式        button.base,        // 第二个样式根据信号值进行判断        login ? button.login : button.logout      ]}></button>    </div>  )}// 使用radium包裹住再进行导出就可以支持了export default Radium(App)
    复制代码

注意:后期使用内联样式比较少,大多数情况下还是使用外链样式

外联样式

使用外链样式有以下三种方案

  • 全局外链样式(所有组件都可以使用)

    /* src/style.css  新建一个全局的样式css文件 */.box {  width: 100px;  height: 100px;  background: pink;}
    复制代码
    // src/index.js  入口文件中直接引入并使用这个全局样式文件import React from 'react'import ReactDOM from 'react-dom'import App from './App'import './style.css'ReactDOM.render(  <React.StrictMode>    <App />  </React.StrictMode>,  document.getElementById('root'))
    复制代码
    // src/App.js  在组件中使用即可,会自动获取样式function App() {  return (    <div>      {/* 这里要使用className设置类名,两种写法,我更倾向第一种,老师用的第二种 */}      <p className="box"></p>      <p className={'box'}></p>    </div>  )}export default App
    复制代码
  • 局部外链样式(单组件使用)

    /* src/App.module.css  新建一个和组件名相关的css文件(老师推荐组件名+module.css) */.box {  width: 100px;  height: 100px;  background: pink;}
    复制代码
    // src/App.js  想应组件中引入样式文件,注意这里需要引入为一个对象import style from './App.module.css'function App() {  return (    <div>      {/* 使用的时候要解构这个对象 */}      <p className={style.box}></p>    </div>  )}export default App
    复制代码
  • CSS In JS:使用styled-components包,组件较小可以考虑使用

    安装styled-components:npm install --save styled-components

    // src/App.js 组件中使用styled-components在js内部书写css// 引入 styled-componentsimport styled from 'styled-components'// 利用styled-components创建标签// styled.div表示创建一个div标签const Box = styled.div.attrs({  // attrs可以添加标签属性  className: 'box',  title: '我是styled创建的'})`  width: 100px;  height: 100px;  background: pink;`// styled.div最后跟上模版字符串可以在内部书写样式function App() {  return (    <div>      {/* 最后把新建的styled-components当作组件使用即可 */}      <Box />    </div>  )}export default App
    复制代码

组件创建方式

创建组件有两种方式(使用方法都相同):

  • 函数组件:将组件通过函数return返回

    // 组件名即函数名function App() {  // 使用return返回组件内容即可  return (    <div>      内容    </div>  )}export default App
    复制代码
  • 类组件:将组件通过类继承的方式创建

    // src/about.js  新建一个组件// 引入react 和 Componentimport React, {Component} from 'react'// 使用类继承的方式创建类组件class About extends Component {  // 使用render方法创建组件  render() {    return (      // 返回组件内容      <p>About组件内容</p>    )  }}// 导出组件export default About
    复制代码

如果不想在组件外面使用顶级标签可以使用Fragment或者语法糖<>组件内容</>的方式避免过多顶层标签的出现,个人偏向语法糖写法

// Fragment需要引入import React, {Component, Fragment} from 'react'return (  // 返回组件内容  <Fragment>About组件内容</Fragment>)//  语法糖直接不写顶层标签名即可return (  // 返回组件内容  <>About组件内容</>)
复制代码

向组件传递数据

函数组件父向子传值

{/* 父组件直接使用属性传值 */}<Header name="拉勾教育" age="18" />
复制代码
// src/header.js  子组件// 子组件通过函数的参数props接收全部父组件传递过来的值function Header(props) {  return (    <div>      <p>Header 组件内容</p>      {/* 直接使用props的属性即可 */}      <p>{props.name}</p>      <p>{props.age}</p>    </div>  )}export default Header
复制代码

可以将要传递的数据统一管理在一个对象中,然后通过解构方式传递,更简洁

// 把要传递的数据统一管理const data = {  name: "拉勾教育",  age: "18"}function App() {  return (    <div>      {/* 通过ES6的语法打开数据 */}      <Header {...data} />    </div>  )}
复制代码

同样接收数据的时候也可以做一个解构处理

// 参数部分直接解构传递过来的数据function Header({name, age}) {  return (    <div>      <p>Header 组件内容</p>      {/* 直接使用结构后的数据或者还按照之前的写法 */}      <p>{name}</p>      <p>{age}</p>    </div>  )}export default Header
复制代码

类组件父向子传值

{/* 父组件直接使用属性传值,和函数组件相同 */}<Header name="拉勾教育" age="18" />
复制代码
import React, {Component} from 'react'class About extends Component {   render() {    // 类组件内部会有一个props属性可以获取到全部数据    console.log(this.props)    return (      <>        {/* 直接使用即可 */}        <p>{this.props.name}</p>        <p>{this.props.age}</p>      </>    )  }}export default About
复制代码

同样可以把所有数据统一管理到一个对象中

// 把要传递的数据统一管理const data = {  name: "拉勾教育",  age: "18"}function App() {  return (    <div>      {/* 通过ES6的语法打开数据 */}      <About {...data} />    </div>  )}
复制代码

同样可以通过解构来处理

import React, {Component} from 'react'class About extends Component {   render() {    const {name, age} = this.props    return (      <>        {/* 直接使用即可 */}        <p>{name}</p>        <p>{age}</p>      </>    )  }}export default About
复制代码

个人偏向于

  • 使用对象统一管理数据
  • 通过函数组件的方式创建组件
  • 通过ES6打开对象的方式传值
  • 通过解构的方式解构数据直接使用

Props默认值设置

函数组件设置props默认值

直接使用组件名.defaultProps = {参数名:默认值........}设置

function Header(props) {  const {name, age} = props  return (    <div>      <p>Header 组件内容</p>      {/*  */}      <p>{name}</p>      <p>{age}</p>    </div>  )}// 直接设置默认值!!!!!!!!!!!!!!!!!!!!Header.defaultProps = {  name: '111',  age: 111}export default Header
复制代码

类组件设置props默认值

在类内部设置静态属性defaultProps即可

import React, {Component} from 'react'class About extends Component {  // 直接设置静态属性即可设置默认值!!!!!!!!!!!!!!!!!  static defaultProps = {    name: '111',    age: 111  }  render() {    const {name, age} = this.props    return (      <>        <p>{name}</p>        <p>{age}</p>      </>    )  }}export default About
复制代码

props类型校验

类型校验需要使用第三方包prop-types进行处理

安装依赖:npm i prop-types --save中文文档

// 引入依赖获得校验功能import PropTypes from 'prop-types'function Header(props) {  const {name, age} = props  return (    <div>      <p>Header 组件内容</p>      {/*  */}      <p>{name}</p>      <p>{age}</p>    </div>  )}Header.defaultProps = {  name: '111',  age: 111}// 进行数据校验// 注意这里的 Header.propTypes和下面的 PropTypes不是同一个Header.propTypes = {  // PropTypes.string:字符串类型  // isRequired:必须  name: PropTypes.string.isRequired,  age: PropTypes.number}export default Header
复制代码

老师没有演示类组件的校验方法

向组件传递JSX

当父组件向子组件传递了JSX语法结构,那么需要在组件中使用props.children获取

// 引入新建的组件import About from './about'import Header from './header'// 把要传递的数据统一管理const data = {  name: "拉勾教育",  age: "18"}function App() {  return (    <div>      {/* 向两个子组件传递JSX!!!!!!!!!!!!!!! */}      <About>        <p>App向About传递的JSX</p>        <p>App向About传递的JSX</p>      </About>      <Header>        <p>App向About传递的JSX</p>      </Header>    </div>  )}export default App
复制代码
// 函数组件可以直接从参数props中拿到children获得JSXfunction Header(props) {  return (    <div>      <p>Header 组件内容</p>      <p>{props.name}</p>      <p>{props.age}</p>      {/* 拿JSX!!!!!!!!!!!!!!!!!! */}      {props.children}    </div>  )}
复制代码
// 类组件也可以从this.props.children中拿到JSXclass About extends Component {  static defaultProps = {    name: '111',    age: 111  }  render() {    return (      <>        <p>{this.props.name}</p>        <p>{this.props.age}</p>     	  {/* 拿JSX!!!!!!!!!!!!!!!!!! */}        {this.props.children}      </>    )  }}
复制代码

JSX可以不止一行,可以是多行多个标签

组件布局实例制作

g3tEgx.md.png

  • 实现效果如图
  • 需要制作的组件:Header组件、home组件、List组件、Footer组件
  • 可以考虑将Header和Footer组件进行封装,然后home和List引入封装后的组件通过传入不同的JSX实现不同的内容
  • 组件统一放到新的目录components中

g3rSrn.md.png

// src/components/header.js 创建Header组件,添加类名function Header() {  return (    <>      <p className="header">Header组件内容</p>    </>  )}export default Header// src/components/footer.js  创建Footer组件,添加类名function Footer() {  return (    <>      <p className="footer">Footer组件内容</p>    </>  )}export default Footer
复制代码
// src/components/layout.js  创建Layout封装Footer和Header组件// 引入两个公共组件import Header from './header'import Footer from './footer'// 接收参数function Layout(props) {  return (    <>      <Header />      {/* 使用父组件传进来的JSX */}      {props.children}      <Footer />    </>  )}export default Layout
复制代码
// src/components/home.js  新建Home组件并引入子组件Layout,传入要显示的JSX// 引入封装的Layout组件import Layout from './layout'function Home() {  return (    <Layout>      {/* 向Layout传递Jsx实现内容的定制 */}      <p className="main">Home组件内容</p>    </Layout>  )}export default Home// src/components/list.js  新建List组件并引入子组件Layout,传入要显示的JSXimport Layout from './layout'function List() {  return (    <Layout>      {/* 向Layout传递Jsx实现内容的定制 */}      <p className="main">List组件内容</p>    </Layout>  )}export default List
复制代码
// 在app.js中使用最后封装的组件// 引入两个子组件Hom和List,两者都封装了Header和Footerimport Home from './components/home'import List from './components/list'function App() {  return (    <div>      {/* 使用组件 */}      <List />    </div>  )}export default App
复制代码
/* src/style.css 新建css文件添加样式 */* {  padding: 0;  margin: 0;}.header {  width: 100%;  height: 100px;  background: skyblue;}.footer {  position: fixed;  bottom: 0;  width: 100%;  height: 100px;  background: #891189;}.main {  width: 100%;  height: calc(100vh - 200px);  background: #f2e79d;}
复制代码
// src/index.js 在index.import React from 'react'import ReactDOM from 'react-dom'import App from './App'// 直接使用刚刚编辑的样式文件作为全局样式import './style.css'ReactDOM.render(  <React.StrictMode>    <App />  </React.StrictMode>,  document.getElementById('root'))
复制代码

总结

布局设置的两种思路

  • 看到什么写什么,都封装为单独组件,然后引入页面
  • 将经常复用的组件抽出来单独封装

组件状态

注意:(默认没有钩子的情况下)函数组件无法保存状态,是无状态组件,如果想保存状态需要修改为类组件

组件状态 :组件内部自己保存的数据

  • 我们将组件自己的状态(数据)让他自己管理
  • 类数组中有一个state属性可用于管理数据
  • 可以使用this.setState方法对数据进行操作,注意这里直接修改this.state无效
// 引入组件,不需要引入react这里省略了import {Component} from 'react'// 创建类组件class Header extends Component {  // 存储数据  state = {    name: 'zs',    age: 10  }  // 按钮点击方法,这里使用箭头函数,否则会报错  clickbtn = () => {    // 调用setState方法进行数据的修改,注意不能直接修改    this.setState({      age: 20    })  }  render() {    // Jsx    return (      <>        {/* 点击按钮修改数据 */}        <button onClick={this.clickbtn}>点击</button>        <p>{this.state.name}</p>        <p>{this.state.age}</p>      </>    )  }}export default Header
复制代码

setState注意事项

  • setState是异步函数

    • 可以使用 sync – await 进行同步处理
    • setState支持传入第二个参数,为一个回调函数,在回调函数内部操作
    // 注意点:setState是异步函数,后面的代码可能会先执行// clickbtn = () => {//   this.setState({//     age: 20//   })//   console.log(this.state.age)  //返回10// }// 解决方案1:使用 async - await 进行同步化处理,让后面的代码等待// clickbtn = async () => {//   await this.setState({//     age: 20//   })//   console.log(this.state.age)  //返回20// }// 解决方案2:使用setStatr的第二个参数,支持传入一个函数,函数会等待前面运行完之后再运行,相当于then方法clickbtn = () => {  this.setState({    age: 20  }, () => console.log(this.state.age))  //返回20}
    复制代码
  • setState第一个参数可以传入一个函数

    import {Component} from 'react'class Header extends Component {  state = {    num: 0  }  // 常规第一个参数为对象  // add = () => {  //   this.setState({  //     num: this.state.num + 1  //   })  // }  // setState第一个参数也可以是一个函数,函数最终返回和上面一样的对象  add = () => {    this.setState((state) => ({      num: state.num + 1    }))  }    render() {    // 下面是一个计数器功能    return (      <>        {this.state.num}        {/* 点击按钮实现数字+1 */}        <button onClick={this.add}>+1</button>      </>    )  }}export default Header
    复制代码
  • setState如果第一个函数传入的是函数,那么多个setState都可以触发,反之如果传入的是一个对象,只有最后一个setState可以触发

    // 如果第一个参数使用函数,那么多个setState都可以触发add = () => {  this.setState((state) => ({    num: state.num + 1  }))  this.setState((state) => ({    num: state.num + 5  }))  // 最终点击一次+6}// 如果第一个参数是对象,只有最后一个setState可以触发add = () => {  this.setState({    num: this.state.num + 1  })  this.setState({    num: this.state.num + 5  })  // 最终点击一次只能+5}
    复制代码

推荐使用函数

组件中的this

组件函数中,如果使用function声明函数的时候,this指向undefined

add = function () {    console.log(this)  //this指向undefined  }
复制代码

解决方法:

  • 函数定义使用箭头函数

    add = () => {  console.log(this)  //this指向组件}
    复制代码
  • 函数调用的时候直接使用箭头函数调用

    // 使用箭头函数调用函数<button onClick={() =>{this.add()}}>+1</button>add = function () {  console.log(this)  //this指向undefined}
    复制代码
  • 利用bind进行绑定

    // 利用bind绑定<button onClick={this.add.bind(this)}>+1</button>add = function () {  console.log(this)  //this指向undefined}
    复制代码

单向数据流动

  • 单向数据流动:即数据可以根据组件的嵌套关系,自顶而下的传递数据,而每一层组件只需要将接收到的数据传递下去即可
  • 单向数据流修改数据:单向数据流中子组件可以使用父组件传递过来的方法来修改数据(前提是父组件传递过来的数据中有相应的方法)
  • 特点:在单向数据流中,一旦某一条数据发生变化,整个流动路径都会发生数据更新重新渲染

gGK4nH.md.png

// src/App.js  单向数据流源头// 因为 App 要保存状态(数据),所以需要设置为类组件// 引入组件import {Component} from 'react'// 引入C1组件import C1 from './C1'// App组件class App extends Component {  // 状态数据  state =  {    name: 'cwn',    age: 28  }  // 修改状态数据的方法,这里利用了ES6的语法对参数进行了结构  chageState = ({name, age}) => {    // 这里利用ES6新语法 - 状态名和值相同可简写    this.setState({name,age})  }  render() {    return (      <div>        {/* 把数据和修改方法传递给C1组件 */}        <C1 {...this.state} chageState={this.chageState}/>      </div>    )  }}// 导出export default App
复制代码
// src/C1.js  单向数据流第一次流动// 引入C2组件import C2 from './C2'// C1组件,注意形参function C1(props) {  // 打印父组件传递过来的数据看一下  console.log(props)  return (    <>      <p>C1组件</p>      {/* 将父组件传递过来的参数继续向下传递 */}      <C2 {...props}/>    </>  )}// 导出export default C1
复制代码
// src/C2.js  单向数据流第二次流动// 引入C3组件import C3 from './C3'// C2组件function C2(props) {  // 打印父组件传递过来的数据看一下  console.log(props)  return (    <>      <p>C2组件</p>      {/* 将父组件传递过来的参数继续向下传递 */}      <C3 {...props}/>    </>  )}// 导出export default C2
复制代码
// src/C3.js  单向数据流最后一次流动,这里进行数据修改操作function C3(props) {  // 打印父组件传递过来的数据看一下  console.log(props)  return (    <>      <p>C3组件</p>      {/* 按钮,点击后触发App一路传递过来的数据中的 chageState 方法修改数据 */}      <button onClick={() => props.chageState({name: 'ccc', age: 20})}>修改数据</button>      {/* 注意这里写成箭头函数,否则会出问题 */}    </>  )}export default C3// 单向数据流动:App -> C1 -> C2 -> C3// C3实现修改数据:App中定义修改数据的方法 -> 方法随着数据流一路向下 -> 最终C3调用这个方法并传递数据// 单向流动特点:当C3修改了数据后。C1、C2、C3中的 “console.log(props)” 又被触发了一次,说明修改单向流数据会导致整个流动路径重新渲染// 好处:单向流便于管理,总能找到源头,便于查找错误
复制代码

受控表单 – 绑定与更新

  • 表单分类
    • 受控表单:表单的值全部由React管理,此时React把数据放置在state中,表单通过this.state获取
    • 非受控表单(不常用):值不由React管理,全部由DOM自己管理,获取需要操作DOM元素
  • 受控表单的数据绑定和修改
    • 受控表单数据通过state进行获取
    • 想要更新值可以使用input的onChage事件,此时需要传递事件对象
    • 修改值可以采用两种方式
      • 直接在DOM中书写事件函数
      • 单独书写事件函数,触发事件的时候调用(此时需要获取需要更新的值的名字)
  • 细节:如果我们input标签不写onChage事件可能会报错,可以使用readOnluy或者defaultValue处理
// 引入组件import {Component} from 'react'// App组件class App extends Component {  // 状态数据  state =  {    name: 'cwn',    age: 28,    num: 0  }  // 更改数据方法,e为事件对象  chagedData = (e) => {    // 调用 setState 修改数据    this.setState({      // 这里使用动态属性名获取DOm元素name属性,属性值也是动态的      [e.target.name]: e.target.value    })  }  render() {    return (      <div>        {/* 修改数据方法1:直接写在Dom元素内部(语法较短可以考虑) */}        <input value={this.state.name} onChange={(e) => this.setState({name: e.target.value})}/>                {/* 修改数据方法2:事件调用方法,然后把事件对象传递过去 */}        <input name="name" value={this.state.name} onChange={(e) => this.chagedData(e)}/>        <input name="age" value={this.state.age} onChange={(e) => this.chagedData(e)}/>                {/* 如果不写onChange的话React会提示错误,使用 readOnly 或者 defaultValue 可以处理 */}        <input name="age" value={this.state.age} readOnly/>          <input name="age" defaultValue={this.state.age}/>      </div>    )  }}// 导出export default App
复制代码

受控表单 – 下拉菜单

下拉菜单和上面类似,同样需要定义初始状态,然后修改状态的时候也要使用onchange事件处理

// 引入组件import {Component} from 'react'// App组件class App extends Component {  // 状态数据  state =  {    value: 2  }  render() {    return (      <div>        {/* 下拉菜单,value获取状态数据,使用事件进行状态数据修改,e为事件对象 */}        <select value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}>          <option value="1">1</option>          <option value="2">2</option>          <option value="3">3</option>          <option value="4">4</option>        </select>        {/* 随之展示当前value值 */}        {this.state.value}      </div>    )  }}// 导出export default App
复制代码

受控表单 – 单选框

单选框和上面也几乎相同,需要注意的是

  • 两个单选框name要相同才能归结为一组
  • 选中状态使用defaultChecked设置
// 引入组件import {Component} from 'react'// App组件class App extends Component {  // 状态数据  state =  {    sex: true  }  render() {    return (      <div>        {/* 一组单选框,设置相同的name、选中状态依赖于 state.sex 属性,修改时修改状态数据 */}        <input type='radio' name='sex' defaultChecked={this.state.sex} onChange={() => this.setState({sex: true})} />男        <input type='radio' name='sex' defaultChecked={!this.state.sex} onChange={() => this.setState({sex: false})} />女                {/* 实时展示当前sex值 */}        <p>{this.state.sex ? '男' : '女'}</p>      </div>    )  }}// 导出export default App
复制代码

受控表单 – 复选框

  • 复选框可以单独书写,这里会涉及到遍历的问题
  • render里面可以书写js语句,复选框可以使用循环语句创建
// 引入组件import {Component} from 'react'// App组件class App extends Component {  // state状态数据  state = {    name: 'cwn',    age: 18  }  // 复选框数据  option = [    {id: 0, title: 'Vue', select: true},    {id: 1, title: 'React', select: false},    {id: 2, title: 'JS', select: false}  ]  // 复选框点击事件  // index - 数组索引(因为数组id和索引相同就用ID代替了)  // ev - 事件对象  chageCheckbox = (index, ev) => {    // 修改数据    this.option[index].select = ev.target.checked  }  // 表单提交事件  submit = (ev) => {    // 仅用默认行为    ev.preventDefault()    // 获取全部 select 为 true 的 id值(先筛选全部选中的复选框,再从复选框中获取相应id值)    let ret = this.option.filter(item => item.select).map(item => item.id)    // 将要发送的数据进行合并    ret = {...this.state, ret}    // 最后提交(这里暂时不能提交先打印)    console.log(ret)  }  render() {    return (      // form表单,添加表单提交的方法      <form onSubmit={this.submit}>        {/* 遍历数组 */}        {this.option.map(item => {          return (            // 创建结构,key为标记,不写可能会报错            <label key={item.id}>              {/* 复选框,调用数据,添加onchange事件 */}              <input type="checkbox" defaultChecked={item.select} onChange={this.chageCheckbox.bind(this, item.id)} />{item.title}            </label>          )        })}        <hr/>        {/* 在下面实时显示三个复选框选中状态 */}        {this.option.map(item => {          return(            <span key={item.id}>{item.select ? '选中  ' : '未选中  '}</span>          )        })}        {/* 提交按钮 */}        <input type="submit" value="提交" />      </form>    )  }}// 导出export default App
复制代码

非受控表单

  • 非受控表单即表单元素内部数据不被React管理,无法直接通过React获取访问
  • 所以非受控表单数据想要获取只能先获取到表单元素,才能获取内部数据
// 引入组件import {Component} from 'react'// App组件class App extends Component {  // 表单提交事件  submit = (ev) => {    // 禁用默认行为    ev.preventDefault()    // 打印非受控表单数据    console.log(this.refs.user.value)  }  render() {    return (      <form onSubmit={this.submit}>        {/* 非受控表单,添加ref属性方便后面获取Dom元素 */}        <input type='text' ref='user' />        {/* 提交按钮 */}        <input type='submit' />      </form>    )  }}// 导出export default App// 这里可能会报错,是因为当前React采取了严格模式导致的,取消严格模式(idex.js的<React.StrictMode>)即可解决// 和面还会使用其他方法获取元素
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享