React超级基础入门篇!!

279 阅读14分钟

跟着下面的步骤来!!

shift+鼠标右键 windows PowerShell^ ^

create-react-app my-app(文件名)

cd my-app

npm start

好了,你已经创建了一个react项目文件了捏❥(ゝω・✿ฺ)

1. 工程目录简介

scr目录下

index.js - 入口文件

index.js里可以引入 css 文件,之前的时候说过css文件只能在 html 文件里引入

这个是可以通过webpack配置,就可以在 js 里引入 css 文件了

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
​
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

这里 发现下面没有用到 React 但是不能把 import React from 'react'删掉

因为 ReacDOM.render<App/>用到的是 JSX 语法,要引入 'react'来解析这种语法

还有就是 React.StrincMode相当于开启严格模式,有助于:

  • 识别具有不安全生命周期的组件
  • 有关旧式字符串ref用法的警告
  • 检测意外的副作用
  • 检测遗留 context API

具体参考官方文档(react.caibaojian.com.cn/docs/strict…)

App.js帮助生成文本

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

ReactDOM.render把app挂载到页面上

App.test.js 是自动化测试的文件

2. React 基础入门

1. react 中的组件

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Test from './Test'ReactDOM.render(
  <div>
    <App />
    <Test />
  </div>
 ,
  document.getElementById('root')
);

就这样挂载上去

2. 什么是JSX语法

JSX是对js语法的拓展

JSX 语法里面,有两类型的标签

JSX 第一种标签:普通的 html 标签 ex: div、p

JSX 第二种标签:组件标签,上面 <App />注意:组件的标签是大写的

3. 使用 React 编写 TodoList 功能

import React,{ Fragment } from 'react'function TodoList() {
  return (
    <Fragment>
      <input />
      <ul>
        <li>
          Learn React
        </li>
        <li>
          Learn React2
        </li>
      </ul>
    </Fragment>
  )
}
​
export default TodoList;

Fragment占位符

4. React 中数据驱动的设计思想和事件绑定

import React,{ Component, Fragment } from 'react'class TodoList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      inputValue: 'hello world',
      list: []
    }
  }
  
  render() {
    return (
      <Fragment>
        <input value={this.state.inputValue} />
        <ul>
          <li>Learn React</li>
          <li>Learn React2</li>
        </ul>
      </Fragment>
    )
  }
}
​
export default TodoList;

理解:

constructor 构造函数,根据之前学的 class 自己理解

react里 数据是要写在 this.state里面的

接着上面的 TodoList 当我们在 input 框里输入内容的时候,input 框里的内容要跟着变化,先看上面 input 里绑定的数据是 this.state.inputValue所以不管在输入框里输入什么,显示的始终是this.state.inputValue的值

所以要在 input 框上面绑定 onChange 事件

  handleInputChange(e) {
    console.log(e.target.value)
  }
​
  render() {
    return (
      <Fragment>
        <input 
          value={this.state.inputValue} 
          onChange={this.handleInputChange.bind(this)}
        />
        <ul>
          <li>Learn React</li>
          <li>Learn React2</li>
        </ul>
      </Fragment>
    )
  }
}

在上面的 handleInputChange 里 打印出来的结果是输入框里面的内容

再理解一下 onChange={this.handleInputChange.bind(this)}

如果不用 bind 传入 this 那么,在handleInputChange里打印出的 this 是 undefined

这是为什么呢?

react 类组件中绑定事件处理函数时,相当于 js 中将函数直接作为事件处理函数一样,这时事件应该指向事件源,由于 react-cli 自身的严格模式导致 this 指向 undefined

然后在 react 里面规定 修改 state 里的属性时要调用 setState

  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }

这样 input 框里的内容会根据输入的内容改变了

5. 实现 TodoList 新增删除功能

首先是ul里的li标签内容应该是循环展示 list 里的值

<ul>
  {
    this.state.list.map((value, index) => {
      return <li key={index}>{value}</li>
    })
  }
</ul>

这个 key 后面应该会说到时候来补充^ ^(暂时理解为跟 vue 里的key相同)

然后是当我们按下 enter 的时候,我们输入的内容展示在下面的li标签里

<input 
  value={this.state.inputValue} 
  onChange={this.handleInputChange.bind(this)}
  onKeyUp={this.handleKeyUp.bind(this)}
/>

先在 input 框里面绑定 onKeyUp 事件

handleKeyUp(e) {
  console.log(e.keyCode)
}

这样可以在控制台里获得 enter 键的键码 -> 13

handleKeyUp(e) {
  if(e.keyCode === 13) {
    const list = [...this.state.list, this.state.inputValue]
    this.setState({
      list: list
    })
  }
}

先把原先 list 的值拷贝一份,然后把新的值加进去,然后再修改list

可以简化一下上面的代码 list: list可以写成list

另一个优化就是,当我们 enter 之后,输入框里的内容应该清空

handleKeyUp(e) {
  if(e.keyCode === 13) {
    const list = [...this.state.list, this.state.inputValue]
    this.setState({
      list,
      inputValue: ''
    })
  }
}

这样添加的功能就ok惹^ ^

删除功能:点击li标签,对应的内容就被删除掉

先在li标签上绑定 onclick 事件(注意看格式)

<ul>
  {
    this.state.list.map((value, index) => {
      return (
        <li key={index} onClick={this.handleItemClick.bind(this, index)}>
          {value}
        </li>
      )
    })
  }
</ul>

return后面的内容加个括号这样就可以换行,bind 方法里可以传参

handleItemClick(index) {
  const list = [...this.state.list]
  list.splice(index, 1)
  this.setState({list})
}

还是先把this.state.list里的值拷贝一份,然后用splice方法删掉指定的一项

6. 更多JSX语法细节

对上面的 TodoList 进行优化

首先回顾一下 bind 函数,bind 函数传入的参数分成两部分,第一个参数是函数上下文作用的对象,后面则是参数列表。bind 函数是返回一个改变了 this 指向的函数,而原函数的 this 没有被改变

每执行一次点击或者change事件,都要执行一次bind函数,是非常消耗性能的,我们可以把这一步写在constructor里面

constructor(props) {
  super(props)
  this.handleInputChange = this.handleInputChange.bind(this)
  this.handleKeyUp = this.handleKeyUp.bind(this)
  this.state = {
    inputValue: 'hello world',
    list: []
  }
}

handleItemClick不能写在ocnstructor里面,因为这个方法要传入参数,每次传递的参数是不同的

然后就是在render()函数里,对li标签的循环我们可以单独写在一个函数里

getItemList() {
  return this.state.list.map((value, index) => {
    return (
      <li key={index} onClick={this.handleItemClick.bind(this, index)}>
        {value}
      </li>
    )
  })
}
​
render() {
  return (
    <Fragment>
      <input 
        value={this.state.inputValue} 
        onChange={this.handleInputChange}
        onKeyUp={this.handleKeyUp}
      />
      <ul>
        {this.getItemList()}
      </ul>
    </Fragment>
  )
}

这样基本上就优化完成了,然后贴一下完整的代码

TodoList.js

import React,{ Component, Fragment } from 'react'class TodoList extends Component {
  constructor(props) {
    super(props)
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleKeyUp = this.handleKeyUp.bind(this)
    this.state = {
      inputValue: 'hello world',
      list: []
    }
  }
  
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }
​
  handleKeyUp(e) {
    if(e.keyCode === 13) {
      const list = [...this.state.list, this.state.inputValue]
      this.setState({
        list,
        inputValue: ''
      })
    }
  }
​
  handleItemClick(index) {
    const list = [...this.state.list]
    list.splice(index, 1)
    this.setState({list})
  }
​
  getItemList() {
    return this.state.list.map((value, index) => {
      return (
        <li key={index} onClick={this.handleItemClick.bind(this, index)}>
          {value}
        </li>
      )
    })
  }
​
  render() {
    return (
      <Fragment>
        <input 
          value={this.state.inputValue} 
          onChange={this.handleInputChange}
          onKeyUp={this.handleKeyUp}
        />
        <ul>
          {this.getItemList()}
        </ul>
      </Fragment>
    )
  }
}
​
export default TodoList;

  1. 假如我们在 TodoList 里面引入一个 css 文件

    style.css

    .input {
      border: 1px solid green;
    }
    

    如果我们在 input 标签上给它添加 class='input'控制台会报错,因为我们这个组件是 class 组件,两个都叫 class 可能会混淆,所以在 input 标签上给它添加类名的时候应该写成 className='input'

  2. 在输入框前面加一个请输入内容:然后点击这段文字,输入框就就会聚焦

    <label for='myinput'>请输入内容:</label>
    <input 
      id='myinput'
      className='input'
      value={this.state.inputValue} 
      onChange={this.handleInputChange}
      onKeyUp={this.handleKeyUp}
    />
    

    如果用for控制台就会报错,因为在js语法里面for是一个循环语句,所以在这里要用htmlFor

  3. 在jsx语法里面写注释 {/* 这是一段注释 */}

  4. 在上面的TodoList里,在 input 框里面直接按回车,下面li标签也会添加空的内容,可以添加一个判断就可以阻止

    handleKeyUp(e) {
      if(e.keyCode === 13 && e.target.value !== '') {
        const list = [...this.state.list, this.state.inputValue]
        this.setState({
          list,
          inputValue: ''
        })
      }
    }
    
  5. 假如我们在输入框里面输入<h1>hello</h1>下面的li标签会对 h1标签进行转义,但是我想做的就是显示h1标签,那么:

    <li 
      key={index} 
      onClick={this.handleItemClick.bind(this, index)}
      dangerouslySetInnerHTML={{__html: value}}
      >
    </li>
    

3. React组件与生命周期

1. 组件拆分与组件之间的传值

接着上面的TodoList,我们可以把下面的li标签拆分成一个一个的组件

TodoItem.js

import React,{Component} from "react";
​
class TodoItem extends Component {
  render() {
    return <li>{this.props.content}</li>
  }
}
​
export default TodoItem

TodoList.js

getItemList() {
  return this.state.list.map((value, index) => {
    return <TodoItem content={value} />
  })
}

对比之前的代码,这里返回的是TodoItem组件 父组件通过属性的形式向子组件传值,子组件用props接收父组件传来的值

上面子组件接收父组件传来的值还可以用es6的解构赋值来写

const {content} = this.props
return <li>{content}</li>

然后是做删除的功能

不能直接在子组件上面绑定click事件,要在子组件里面去绑定

TodoItem.js

import React,{Component} from "react";
​
class TodoItem extends Component {
  constructor(props) {
    super(props)
    this.handleItemClick = this.handleItemClick.bind(this)
  }
​
  handleItemClick() {
    const { deleteFunction, index } = this.props
    deleteFunction(index)
  }
​
  render() {
    return <li onClick={this.handleItemClick}>{this.props.content}</li>
  }
}
​
export default TodoItem

li标签上绑定onClick事件

handleItemClick(index) {
  const list = [...this.state.list]
  list.splice(index, 1)
  this.setState({list})
}
​
getItemList() {
  return this.state.list.map((value, index) => {
    return (
      <TodoItem 
        key={index} 
        index={index}
        content={value} 
        deleteFunction={this.handleItemClick}
      />
    )
  })
}

父组件向子组件传递handleItemClick方法(deleteFunction是属性)

子组件接收到这个方法并调用它,传入当前组件的index值,即调用父组件的handleItemClick方法

父组件通过属性的形式向子组件传值

子组件想要和父组件通信,它要调用父组件传递过来的方法

3. React的核心特性总结

  1. 声明式开发(原生js、jquery是命令式,页面上展示什么需要不断的操作dom)

    也就是我们不需要操作dom,只需要定义好jsx模板和数据,数据发生变化,页面的dom自动就会跟着发生变化

  2. 可以与其他框架并存

    我们把组件挂载到哪个节点上,react只会处理那一个节点,其他的节点是不会处理的,所以其他的节点我们也可以用jquery、vue来处理也是可以的^ ^

  3. 组件化

    页面被拆分成一个一个的组件,通过组件之间的通信来实现整个页面的功能

  4. 单向数据流(vue也是单向数据流^ ^)

    父组件可以改变子组件的数据,子组件一定不能直接改变父组件的数据

    上面的例子

    <TodoItem 
      key={index} 
      index={index}
      content={value} 
      deleteFunction={this.handleItemClick}
    />
    

    当index、value发生变化的时候,子组件接收到的值也就变化了

    但是当子组件想改变父组件的数据时,我们是调用父组件的方法改变父组件的数据(其实也可以说是父组件自己改变自己的数据)

  5. 函数式编程

4. Props, State 与 render 函数

props指的是属性,父组件通过属性的形式向子组件传递参数

state指的是组件里的数据

render函数用来渲染组件里的内容

render()函数什么时候会被执行

  • 当组件初次创建的时候,render函数会被执行一次
  • 当state数据发生变更的时候,render函数会被重新执行
  • 当props数据发生变更的时候,render函数会被重新执行

5. React 中 ref 的使用

  1. ref 写在 html 标签上

    <button 
      onClick={this.handleButtonClick}
      ref={(button) => {this.buttonElem = button}}
    >
      增加
    </button>
    

    获取的是dom节点

    console.log(this.buttonElem)

    打印出来的是

    <button>增加</button>

  2. ref写在组件标签上

    image-20211020185851529.png

    获取的是组件的js实例

  3. 应用

    handleButtonClick() {
      const newCounter = this.state.counter + 1
      console.log(this.divElem.innerHTML)
      this.setState({
        counter: newCounter
      })
      console.log(this.divElem.innerHTML)
    }
    ​
    render() {
      return (
        <Fragment>
          <button 
            onClick={this.handleButtonClick}
          >
            增加
          </button>
          <div
            ref={(div) => {this.divElem = div}}
          >{this.state.counter}</div>
        </Fragment>
      )
    }
    

    上面的第二个console.log(this.divElem.innerHTML)

    理想状态下是按一下按钮 输出1 2但实际上这样写输出的都是1

    因为this.state是异步执行的,也就是后面的console.log先执行,联系到eventloop

    this.setState(() => {
      return {
        counter: newCounter
      }
    }, () => {
      console.log(this.state.counter)
    })
    

    这样写就打印出来的是1 2

    理解一下:

    setState可以接受两种参数,

    第一种:一个对象

    第二种:函数(上面的第二个代码),第一个参数可以修改值,第二个参数是回调函数

6. React 中的生命周期函数

在React中,生命周期函数指的是组件在某一时刻会自动执行的函数

constructor也是生命周期函数,但是它不是React生命周期函数

当数据发生变化时,render函数会被自动执行,render函数是一个react生命周期函数

image-20211020194254127.png

Initialization,组件初始化。执行constructor()注意它不是React生命周期函数

Mounting,挂载阶段。render()函数自动执行,页面第一次渲染的时候,才会执行Mounting整个过程

简单理解一下:

componentWillMount:挂载前

render():挂载

componentDidMount:挂载完成

页面更新的时候,执行的是 Updation,更新阶段。分为两种,一种是props发生变化,另一种是states发生变化

先不管 componentWillRececiveProps,这里还有四个生命周期函数

shouldComponentUpdate() componentWillUpdate() render() componentDidUpdate()

假如,我们触发了一个点击事件,但是数据没有发生变化,但是更新阶段还是会执行这四个函数,会比较消耗性能,我们可以在shouldComponentUpdate()里:

shouldComponentUpdate() {
    return false;
}

这样后面的三个阶段都不会执行了,就比较提升性能^ ^

Unmounting阶段

当组件被移除的时候会执行componentWillUnmount()

componentWillReceiveProps

上面有一个功能,点击按钮,下面的数字加一,数字写在子组件里

渲染页面时,生命周期函数的执行顺序

f constructor
f componentWillMount
f render
c constructor
c componentWillMount
c render
c componentDidMount
f componentDidMount

当点击按钮,触发页面更新的时候

f componentDidMount
f shouldComponentUpdate
f componentWillUpdate
f render
c componentWillReceiveProps
c shouldComponentUpdate
c componentWillUpdate
c render
c componentDidUpdate
f componentDidUpdate

这个componentWillReceiveProps()只会在子组件里面触发,而父组件不会,因为父组件里根本就没有props!!

7. 生命周期函数使用实例

在全局绑定的函数,一定要在componentWillUnmount()销毁掉

使用 axios 发送数据,放在componentDidMount()

8. React 中的前端路由

BrowserRouter

Route

Link

<BrowserRouter>
    <div>
        <Route path='/list' component={NewList} />
        <Route path='/button' component={NewButton} />
    </div>
</BrowserRouter>

在路径里后面加 /list就能跳转到 NewList 组件页面

/button就能跳转到 NewButton 组件页面

假如我们想点击按钮跳转页面

<Link to='/list'>
    <Button type='primary'>按钮</Button>
</link>

不能在 Button 外面包裹a标签

当我们跳转页面的时候想传递一个值

<Link to='/list?a=123'>
    <Button type='primary'>按钮</Button>
</link>

用这种方式的话我们不能直接获得 a=123 只能得到 ?a=123 需要我们自己解析,还有一种方法是通过路径的方式传递参数,在配置路由的时候像下面这样写

<Route path='/list/:id' component={NewList} />

这样 123 匹配的就是 :id

<Link to='/list/123'>

4. React 实现新闻网站

1. 在使用 ant design 时遇到的一些问题

在做顶部导航栏的时候,用axios获取导航栏的数据的时候,页面会有一个闪烁的情况,就是一开始导航栏是展开的,但是很快导航栏就自动折叠了。

我自己的理解是:页面进行第一次渲染的时候,list 是空数组,所以 menu 有一个默认的宽度,但是用 axios 请求到了数据之后,理想状态下的导航栏宽度是大于了初始化时的宽度,然后导航栏就被折叠了。

解决办法是给 menu 设置一个初始宽度o.o

2. 做网站时遇到的一些问题

  1. 配置路由的时候,如果像下面这样配置

    <Content className="content">
      <BrowserRouter>
        <Fragment>
          <Route path='/' component={List} />
          <Route path='/detail' component={Detail} />
        </Fragment>
      </BrowserRouter>
    </Content>
    

    当我们切换路径的时候,/只展示 List,但是/detail,List Detail 两个组件都会展示

    解决方法,要用到路由里的新api Switch

    <Content className="content">
      <BrowserRouter>
        <Switch>
          <Route path='/' component={List} />
          <Route path='/detail' component={Detail} />
        </Switch>
      </BrowserRouter>
    </Content>
    

    这样子写,输入路径/展示 List

    但是输入/detail,仍然展示 List!

    这个 Switch会对输入的路径进行匹配,当我们输/detail时,/ 已经和 List 匹配上了,所以就会展示 List,要解决这个问题只需要把 List 和 Detail的位置调换一下

    <Route path='/detail' component={Detail} />
    <Route path='/' component={List} />
    

    这样子就ok了

  2. 还有就是在导航栏对每个菜单配置路由的时候,先看下面的代码:

    <Menu.Item key={item.id}>
      <Link to='/ccc'>
        {item.title}
      </Link>
    </Menu.Item>
    

    emm 这样写的话,页面会报错

    image-20211022140627270.png

    然后回到父组件

    <Layout style={{ minWidth: 1300, height: '100%' }}>
      <Header className="header">
        <AppHeader />
      </Header>
      <Content className="content">
        <BrowserRouter>
          <Switch>
            <Route path='/detail' component={Detail} />
            <Route path='/' component={List} />
          </Switch>
        </BrowserRouter>
      </Content>
      <Footer className="footer">@copyright-Jennie 2021</Footer>
    </Layout>
    

    这个 List 列表是在 AppHeader这个组件里面的,但是这个组件并没有包裹在BrowserRouter里面,所以Link标签是不能使用的,解决办法

    <BrowserRouter>
      <Layout style={{ minWidth: 1300, height: '100%' }}>
        <Header className="header">
          <AppHeader />
        </Header>
        <Content className="content">
          <Switch>
            <Route path='/detail' component={Detail} />
            <Route path='/' component={List} />
          </Switch>
        </Content>
        <Footer className="footer">@copyright-Jennie 2021</Footer>
      </Layout>
    </BrowserRouter>
    

    BrowserRouter把整个Layout包裹起来

  3. 导航栏跳转路由,整理一下过程:

    先是在导航栏选择不同的选项

    Header index.js

    getMenuItems() {
      return this.state.list.map(item => {
        return (
          <Menu.Item key={item.id}>
            <Link to={`/${item.id}`}>
              {item.title}
            </Link>
          </Menu.Item>
        )
      })
    }
    

    把每个选项用Link标签包裹住,在路由中传入 id 的值,就能跳转到不同的页面

    注意 要在根路径后面加:id

    <Route path='/:id' component={List} />

    然后在 List index.js 里:

    首先是,要根据路由传参,传入的参数不同,展示不同页面的数据

    上面写的有this.props.match.params.id,可以获得传入的参数。要考虑到的一个问题是,我们通过/根路径请求页面时,匹配不到/:id,这时页面就没有数据,但是一般情况下应该是默认展示id = 1的页面,所以要

    <Route path='/:id?' component={List} />这里的?就是这个 id 可传可不传,但是不传的话 默认为 undefined,所以我们应该在请求数据时做一个判断

    componentDidMount() {
      let url = 'http://www.dell-lee.com/react/api/list.json'
      const id = this.props.match.params.id;
      if (id) {
        url = url + '?id=' + id
      }
      axios.get(url)
        .then((res) => {
          this.setState({
            data: res.data.data
          })
        })
    }
    

    如果 id 有,那就拼上,没有的话就为原来的 url ,这个数据的处理后端应该会做,也就是没有传 id 的时候默认显示 id = 1的数据

    但是这样子写,当我们切换导航栏的时候,只有刷新页面,页面的数据才会更新。

    原因是:componentDidMount()在页面挂载完后执行,页面没刷新的情况下,只会挂载一次,所以当点击不同的菜单项时,页面不会重新渲染,所以这个钩子函数只会执行一次。

    解决方法:

    componentWillReceiveProps(nextProps) {
      const id = nextProps.match.params.id
      axios.get('http://www.dell-lee.com/react/api/list.json?id=' + id)
        .then((res) => {
          this.setState({
            data: res.data.data
          })
        })
    }
    

    当我们点击不同的菜单选项,就会传入不同的参数,那么componentWillReceiveProps()这个钩子函数就会执行,所以我们在这里面重新发送请求就ok了

  4. emm 点击 logo 返回默认页面

    自己写,很简单,忽略^ ^

这个新闻网站要结合自己写的代码来看(◕ᴗ◕✿)