React基础 | 青训营笔记

46 阅读9分钟

一、react 基础

  • react是facebook退出的用于构建用户界面的前端js框架

  • react 使用组件构建用户界面

  • 组件就是一个区域,区域内集成了html、css以及js

  • 组件开发的优势

  • 将一个庞大的项目拆分为多个独立单元

  • 组件之间相互独立,有利于程序的维护

  • 组件可以重复使用

1.1 搭建react开发环境

  • 方法1: 利用webpack搭建相关的配置
  • 方法2:利用脚手架进行创建

1.1.1 webpack搭建

  • 需要安装的依赖
{
"devDependencies": {
    "@babel/core": "^7.13.8",
    "@babel/preset-env": "^7.13.9",
    "@babel/preset-react": "^7.12.13",
    "babel-loader": "^8.2.2",
    "html-webpack-plugin": "^4.5.2",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.2"
  }
}
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    mode: "development",
    entry: './src/index.js',
    devtool: 'none',
    output: {
        filename: 'main.js',
        path: path.resolve('dist')
    },
    devServer: {
        port: 3000,
        hot: true
    },
    module: {
        rules: [
            {
                test: /.(js|jsx)$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env', '@babel/preset-react']
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new htmlWebpackPlugin({
            template: './src/index.html'
        })
    ]
}

1.1.2 脚手架搭建

  • 安装npm install -g create-react-app,运行create-react-app 文件名创建项目
  • 跳转到项目中,npm start 运行项目
  • 如果发生冲突,配置.env文件,输入SKIP_PREFLIGHT_check = true保存,再运行

1.2 JSX语法

  • 在组建函数中return的部分就是JSX
  • 01 JSX可以看作是JS语言的拓展,它既不是一个字符串也不是HTML
  • 02 它具备了 js的所有功能,同时还可以转化为HTML,在界面上进行展示
  • -动态显示数据 {}
  • -调用方法:自定义+内置
  • -支持表达式,支持三元表达式,不能使用if表达式
  • -模板字符串
const name1 = '123'
const flage = false
const obj = {
  name: 'zs',
  age: 18
}

function sayHello () {
  return '大家好'
}

function App() {
  return (
    <div>
      <div>{name1}</div>
      <div>name1</div>
      <div>{sayHello()}</div>
      <div>{console.log('123')}</div>
      <div>{Math.random()}</div>
      <div>{1 + 2 + 3}</div>
      <div>{flage ? '123' : '456'}</div>
      <div>{`hello,${name1}`}</div>
      <div>123{/* 这里是注释内容 */}</div>
      <div>{JSON.stringify(obj)}</div>
    </div>
  )
}

export default App;

1.2.1 JSX 语法进阶

  • 01 JSX 本身就是一个表达式
  • 02 JSX 添加属性
  • 字符串属性,双引号包裹
  • 动态属性
  • 03 JSX 添加子元素
  • 04 JSX 只能有一个根元素
  • 05 JSX 使用单标签也需要关闭
const name1 = <div>拉勾</div>
const age = 100

function App() {
  return (
    <div>
      {name1}
      <p title='自定义标题'>添加属性</p>
      <p title={age}>添加动态属性</p>
    </div>
  )
}

1.2.2 JSX事件操作

  • 一、事件绑定

  • -绑定的事件触发形式需要是驼峰命名,on触发的方法={监听事件名称}

  • 二、事件传参

  • -利用箭头函数内部调用监听事件传参

  • -利用bind方法返回新的函数事件进行传参

  • 三、获取事件对象

  • -直接调用函数,打印参数

  • -箭头函数传参

  • -通过bind,如果没有事件对象,但是传参了,获取的最后一个参数就是对象

// const handler = (a, b) => {
//   console.log(a, b);
//   console.log('执行力');
// }
const handler = (a, b,ev) => {
  console.log(a, b);
  console.log(ev);
  console.log('执行力');
}

function App() {
  return (
    <div>
      {/* 事件绑定 */}
      {/* <button onClick={handler}>点击触发事件</button> */}

      {/* 参数传递 */}
      {/* <button onClick={() => {handler(1, 2)}}>点击触发事件</button> */}
      {/* <button onClick={handler.bind(null, 100, 200)}>点击触发事件</button> */}

      {/* 获取事件对象 */}
      {/* <button onClick={handler}>点击触发事件</button> */}
      {/* <button onClick={(ev) => {handler(ev, 1, 2)}}>点击触发事件</button> */}
      <button onClick={handler.bind(this, 1, 2)}>点击触发事件</button>
    </div>
  )
}

export default App;

1.2.3 循环数据

  • jsx中数组可以直接将数组中的数据结构
  • 所以可以在在组件内部通过map进行数据的循环
  • 循环的item数据根据JSX语法进行操作,最后获得数据的数组
  • 直接返回给组件本身,并return
// const arr = [<p>1</p>, <p>22</p>, <p>333</p>]
const arr2 = [
  {
    id: 1,
    name: 'zs',
    age: 18,
    salary: 2000
  },
  {
    id: 2,
    name: 'ls',
    age: 19,
    salary: 2000
  }
]

function App() {
  const content = arr2.map(item => {
    return (
    <li key={item.id}>
      <p>{item.name}</p>
      <p>{item.age}</p>
      <p>{item.salary}</p>
    </li>
    )
  })
  console.log(content);
  return (
    <ul>
      {content}
    </ul>
  )
}

export default App;

1.3 添加样式

1.3.1 添加内联样式

  • 需要在行内设置style属性,<div style={{属性名:属性值}}></div>
  • 可以在外部声明属性对象,然后再引入到行内
  • 由于行内样式本身就不支持伪类操作和媒体查询,所以需要安装radium插件
  • 在组件中引入radium,并且导出时要用radium包裹组件
  • 如果有媒体查询,则在引入组件的显示界面中引入import {StyleRoot} from radium
  • 并且书写StyleRoot标签,然后包裹引入的app组件
  • 当想引入多个自定义的样式时,可以给style属性动态绑定一个数组
// 组件 app
/**
 * 01 设置样式时应当将键值对放置于{},因为最外围的{}只是表示包裹的区域遵循动态显示数据
 * 02 内联默认无法支持伪类以及媒体查询
 * 03 radium
 *    a 导入Radium 函数 将当前需要支持伪类操作的组件进行包裹进行导出
*/
import Radium from 'radium'

const styleObj = {
  width: '100px',
  height: '50px',
  backgroundColor: '#ccc',
  ':hover': {backgroundColor: 'skyblue'},
  '@media (max-width: 1000px)': {backgroundColor: 'red'}
}
function App() {
  return (
    <div>
      {/* <div style={{width: '100px',height: '50px',backgroundColor: '#aaa'}}>内联样式</div> */}
      <div style={styleObj}>内联样式</div>
    </div>
  )
}

export default Radium(App)
// index.js
...
import App from './App';
import {StyleRoot} from 'radium'

ReactDOM.render(
  <React.StrictMode>
    <StyleRoot><App /></StyleRoot>
  </React.StrictMode>,
  document.getElementById('root')
);

1.3.2 外联样式

  • 添加类名时,通过className属性进行定义

1.3.2.1 全局外联样式

  • 在index.js全局中引入样式,并在组件中用className={‘类名’}进行类名创建
  • 所有组件均可使用

1.3.2.2 组件级别的外联样式

  • 只在某一个组件中生效
  • 命名时,组件名.module.css
  • 在相应组件中引入样式并用变量接收
  • 使用时,变量.属性名即为属性

1.3.2.3 在js中书写样式

  • 安装styled-components依赖,在需要内部书写样式的组件中引入依赖中的方法并用变量接收
  • 最简单的书写语法格式:变量.标签名模板字符串中书写属性,返回值即为标签
  • 变量.标签名.attrs模板字符串中书写属性
  • 接收的变量首字母一定要大写
import React from 'react'
import test from './test.module.css'
import styled from 'styled-components'

const SectionDiv = styled.div`
    width: 100px;
    height: 100px;
    background-color: hotpink
`

//也可以通过 attrs设置属性后再设置样式
const SectionDi = styled.div.attrs({
    className: 'box1 box2'
})`
    width: 100px;
    height: 100px;
    background-color: hotpink;

    :hover {
        color: red;
        width: 200px;
        background-color: red;
    }
`

function Test () {
    return (
        <div>
            <div className={'box'}>465</div>
            <div className={test.item}>789</div>
            <SectionDiv />
            <SectionDi>123</SectionDi>
        </div>
    )
}

export default Test

1.4 组件操作

1.4.1 组件创建

  • 创建函数组件
  • 创建类组件
  • 引入React和component
  • 类中必须要有render方法
  • 必须继承Component render
  • 组件名首字母必须大写,在react中可以区分组件和普通的标记
  • 最外层要有一个根元素,所以要使用占位符<Fragment>代替组件根元素的DIv,需要引入Fragment
  • 也可以简写为<>内容<>
import React, {Component, Fragment} from 'react'

class About extends Component {
    render () {
        return (
            // <Fragment>About 组件</Fragment>
            <>About 组件</>
        )
    }
}

export default About

1.4.2 组件数据传递

  • 由于创建组建的方式有两种方法,所以传递数据的方法有两种
  • 在组件标签上身上添加属性然后传递数据,这种传递方法会使标签变的特别长,不美观
  • 类组件中内部存在props属性储存外部传递的数据
  • 函数组建的接收的参数就是传递数据整体对象
  • 在外面声明对象存储数据,在组件标签中通过动态解构进行传递
// app.js
const obj = {
  name: 'zs',
  age: 22
}

function App() {
  return (
    <div>
      {/* <About a={'天道酬勤'} b={100} /> */}
      <About {...obj} />
      {/* <Header name={'990624'} age={456} /> */}
      <Header {...obj} />
    </div>
  )
}

// header.js
class Header extends Component {
    render() {
        const {name, age} = this.props
        return (
            <>
                <p>{console.log(this.props)}</p>
                <p>{name}</p>
                <p>{this.props.age}</p>
                <p>{age}</p>
            </>
        )
    }
}

// about.js
// function About (props) {
//     console.log(props);
//     return (
//         <div>
//             <p>{props.name}</p>
//             <p>{props.age}</p>
//         </div>
//     )
// }
function About ({age, name}) {
    return (
        <div>
            <p>{name}</p>
            <p>{age}</p>
        </div>
    )
}

1.4.3 默认值以及类型校验

  • 针对于函数组件来说,想要设置默认的 props属性值,则直接通过 组件名.defaultProps = {属性名:属性值}直接设置
  • 准对于类组件来说可以直接定义 static defaultProps 来管理需要设置的默认属性值即可
// 函数组件
function About ({age, name}) {
    return (
        <div>
            <p>{name}</p>
            <p>{age}</p>
        </div>
    )
}

About.defaultProps = {
    name: 'syy',
    age: 18
}

// 类组件
class Header extends Component {
    static defaultProps = {
        name: 'zs',
        age: 19
    }
    render() {
        const {name, age} = this.props
        return (
            <>
                <p>{console.log(this.props)}</p>
                <p>{name}</p>
                <p>{age}</p>
            </>
        )
    }
}
  • 数据的校验是必要的,因为js中的数据编写时无法确定数据类型,所以需要设置类型范畴
  • 借助prop-types包进行校验,引入PropTypes
  • 函数组件中通过组件名.propTypes = {属性名:PropTypes.类型名.isRequired}表示必填
  • 类组件也是一样的外部校验
// 函数组件校验
function About ({age, name}) {
    return (
        <div>
            <p>{name}</p>
            <p>{age}</p>
        </div>
    )
}

About.propTypes = {
    name: PropTypes.string
}

1.4.4 向组件传递JSX

  • 需要双标签进行包裹
  • 包裹的内容即为传递的参数
  • JSX存放在props中的children中
// app.js
function App() {
  return (
    <div>
      <Header>
        <p>header组件中的p标签</p>
        <span>header组件中的span</span>
      </Header>
      <About>
        <p>about组件中的p标签</p>
        <span>about组件中的s</span>
      </About>
    </div>
  )
}

// header.js
class Header extends Component {
    static defaultProps = {
        name: '大梦谁先绝',
        age: 19
    }
    render() {
        const { name, age } = this.props
        return (
            <>
                <p>{console.log(this.props)}</p>
                <p>{name}</p>
                <p>{age}</p>
                {this.props.children}
            </>
        )
    }
}

// about.js
function About (props) {
    return (
        <div>
            <p>{props.name}</p>
            <p>{props.age}</p>
            {props.children}
        </div>
    )
}

1.4.5 组件布局实例

  • 可以在组件中进行导入
  • 如果是复用的组件可以单独书写到一个组件中,利用JSX传值进行使用,最后将组合好的组件导入App.js中
  • 定义全局样式
// app.js
import Home from './components/home'

function App() {
    return (
        <>
            <Home />
        </>
    )
}

// layout.js
import Header from './Header'
import Footer from './footer'

function Layout (props) {
    return (
        <>
            <Header />
            {props.children}
            <Footer />
        </>
    )
}

// home
import Layout from './layout'

function Home() {
    return (
        <Layout>
            <div>home 组件</div>
        </Layout>
    )
}

1.4.6 组件状态

  • 组件状态指的就是数据,因此组件状态指的就是莫格组件自己的数据

  • 数据驱动DOM:当修改数据时,界面上的DOM中数据也会更新

  • 通过组件状态管理进行实现,将数据交给组件自己进行操作

  • 在类组件中存在一个state属性,它是一个对象,目的是存储当前组件的数据

  • 之后还可以通过 setState方法来修改数据,最后修改后结合状态自动展示在DOM中

  • 通过 this.state.属性名 进行访问

import React, { Component } from 'react'

class Header extends Component {
    state = {
        name: 'zs',
        age: 18
    }
    handler = () => {
        // 在 react 中 不能直接修改state中数据的值
        // this.setState内部传入的是键值对
        this.setState({
            name: 'ls'
        })

    }
    render() {
        return (
            <>
                {this.state.name}
                {this.state.age}
                <button onClick={this.handler}>点击</button>
            </>
        )
    }
}

export default Header

1.4.7 setState使用

  • setState方法默认是异步函数,后面要是使用新数据不行

  • 通过async...await进行包裹,事件运行完后往下执行下面的事件

  • 也可以通过setState的第二个参数(回调函数)进行操作

  • setState 在使用的时候除了可以传入对象之外还可以传入函数

  • setState修改数据时需要通过对象进行修改,要有state的属性名和修改的数据

  • 当setState修改数据

  • 通过函数传递时,执行几次操作几次

  • 通过对象修改时,会进行合并,后面的会覆盖前面的

import React, { Component } from 'react'

class Header extends Component {
    state = {
        name: 'zs',
        age: 18,
        count: 0
    }
    handler = () => {
        // 语法糖执行事件
        // await this.setState({
        //     name: 'ls'
        // })
        // console.log(this.state.name)

        // 第二个参数回调函数执行
        // this.setState({
        //     name: 'ls'
        // }, () => {
        //     console.log(this.state.name, 2222)
        // })

        // setState传递函数
        // 内部的参数是state状态
        // this.setState((state) => (
        //     {
        //         count: state.count + 1,
        //         name: 'ls'
        //     }
        // ))
        // this.setState((state) => (
        //     {
        //         count: state.count + 1,
        //         name: 'ls'
        //     }
        // ))

        this.setState({
            count: 5
        })
        this.setState({
            count: 1
        })
    }
    render() {
        return (
            <>
                <div>
                    {this.state.name}
                    {this.state.age}
                </div>
                <hr />
                <div>
                    {this.state.count}
                    <button onClick={this.handler}>点击</button>
                </div>
            </>
        )
    }
}

export default Header

1.4.8 组件 this指向

  • 在行内使用方法时,方法是箭头函数时指向是组件;一般函数时this未定义,如果想使用this,那么需要行内用箭头函数包裹或者bind改变this指向

1.5 数据单向流动

  • 单项数据流的设计原则
  • 要求我们将不同的组件前需要共享的数据定义在上层
  • 上层的数据需要以类组件的形式,通过state属性进行存储,然后传递
  • 单项数据流动如何修改
  • 定义状态的更新方法,定义箭头函数(this指向问题),接收参数target,在想要修改数据的地方进行调用
  • 子组件通过调用父组件传递过来的方法可以更改数据
  • 当数据发生更改之后,react会重新渲染组件树
// c1.js

import Radium from 'radium'
import C1 from './c1'
import React, {Component} from 'react'


class App extends Component {
  state = {
    name: 'zs',
    age: 18
  }
  handler =  (target) => {
    this.setState({ name: target.name, age: target.age })
  }
  render () {
    return (
      <div>
        <C1 {...this.state} change={this.handler} />
      </div>
  )
  }
}

export default Radium(App)
// App.js
import Radium from 'radium'
import C1 from './c1'
import React, { Component } from 'react'


class App extends Component {
  state = {
    name: 'zs',
    age: 18
  }
  handler = (target) => {
    this.setState({ name: target.name, age: target.age })
  }
  render() {
    return (
      <div>
        <C1 {...this.state} change={this.handler} />
      </div>
    )
  }
}

export default Radium(App)