一文带你玩转React基础

127 阅读10分钟

create-react-app脚手架安装和起步

create-react-app脚手架安装

用cnpm安装(下载速度快)

npm config set registry registry.npm.taobao.org

配置后通过以下方法验证是否成功

npm config get registry

换镜像成功

然后执行命令 sudo cnpm i -g create-react-app // 给予管路员权限 采用cnpm 全局安装

npm安装

npm i -g create-react-app

create-react-app --version 查看是否安装成功(有版本号则为成功)

用脚手架创建项目

create-react-app 项目名

举例创建01.base

create-react-app 01.base

npm或yarn start(有哪个用哪个 推荐yarn) 启动项目

yarn start

简单编写react项目网页

1.将src目录下的所有文件删除 (会报错说没有index文件)

2.创建一个index.js文件(这个就是项目的入口文件)

3.引入react、跟react-dom模块(create-react-app已经下载好的)

4.用ReactDom的render方法插入react组件

index.js代码

//项目的入口文件
import React from 'react';
import ReactDom from 'react-dom';

const ele = <h2>hello React</h2>;
ReactDom.render(ele,document.querySelector('#root'))
这里的#root是public目录下index.html里的一个标签

JSX介绍和模板语法渲染

JSX是什么

上面index.js文件里的ele就是一个JSX对象

JSX==JavaScript+Xml 对象 虚拟Dom元素

模板语法的渲染

JSX里面可以使用变量及函数 只需要用{}括起来(vue使用双大括号插值法)

function findPeople(user){
  return `${user.name}  您的年龄:${user.age}`;
}
const user = {
  name:'QaQ',
  age:'20'
}
const ele = <h2>hello {findPeople(user)}</h2>;
ReactDom.render(ele, document.querySelector('#root'))

也可以在ReactDom.render方法下的第一个参数 用闭合标签括起来使用变量或函数

function findPeople(user){
  return `${user.name}  您的年龄:${user.age}`;
}
const user = {
  name:'QaQ',
  age:'20'
}
function getPeople(user){
  if(user){
    return <h1>hello,{findPeople(user)}</h1>;
  }
  return <h1>hello,stanger</h1>
}
ReactDom.render(<div>{getPeople(user)}</div>, document.querySelector('#root'))

当getPeople实参有值就会出现跟上图一样,无实参的时候

React更新机制

React只更新它需要更新的部分

function tick(){
  const element = (
    <div>
      <h1>Hello React</h1>
      <h2>{new Date().toLocaleTimeString()}</h2>
    </div>
  )
  ReactDom.render(element,document.querySelector('#root'));
}

setInterval(tick, 1000);

React中的遍历绑定与过滤

遍历绑定

创建一个jsx元素,jsx中使用map方法来遍历元素(foreach方法没有return map方法有)

一定要绑定key(key是一个能标识独一无二的值,可以参照vue中的:key)

写法:写在标签里 key={key值}

const arr = [1, 2, 3, 4, 5];
const ele = (
  <ul>
    {arr.map((item, index) => {
      // 循环绑定的jsx元素 必须要有key属性 来区分不同的元素 否则会报错
      return <li key={index}>{item}</li>
    })}
  </ul>
)
ReactDom.render(ele, document.querySelector('#root'))

过滤

同理 filter方法不提供返回值,用map方法搞定。
思路:可以使用三目运算符 不符合条件的为null;也可以使用if语句进行操作

const goods = [
  { id: 1, price: 1000, title: 'iPhone5' },
  { id: 3, price: 1400, title: 'iPhone7' },
  { id: 5, price: 1600, title: 'iPhone9' },
  { id: 6, price: 1700, title: 'iPhone10' },
]
const filterEle = (
  <ul>
    {goods.map((item,index)=>{
      return (item.price > 1400) ? <li key={item.id}>{item.title} </li> : null;
    })}
  </ul>
)
ReactDom.render(filterEle, document.querySelector('#root'))

两种组件创建方式

1.函数声明

  • 组件名称,首字母一定要大写,不然会报错

  • 函数声明 又称函数式组件 本质就是一个函数

  • 必须要有return 返回一个jsx元素

  • ReactDom.render中第一个参数直接用<函数声明的组件名>来指定要渲染的组件

  • ReactDom.render中第一个参数可以定义属性(下面例子的name),在函数式组件的实参props来接收属性,在jsx中使用props.属性名来获取属性值

function Welcome(props){
   //当前函数声明的组件 一定要有return值
   return <h2>Hello {props.name}</h2>
}
ReactDom.render(<Welcome name='Welcome'/>, document.querySelector('#root'))

2.类声明(真实项目中应用最多)

  • 写法:class 组件名 extends React.Component{}

  • React.Component它是一个基类,实用类声明的组件,必须继承这个基类

  • 必须有一个render函数 里面返回一个jsx元素

  • 用this.props.属性名 来获取ReactDom.render第一个参数里定义的属性值

  • 如果有constructor 一定有个props 且一定要super继承props属性

class App extends React.Component {
  //如果有constructor 一定有个props 且一定要super继承props属性
  // constructor(props){
  //   super(props);
  // }
  //必须使用render函数 能将虚拟DOM渲染成真实的DOM
  render() {
    // 它会将jsx所接收的属性转化为单个对象传递到组件,这个对象称为props
    return <h2>App {this.props.name}</h2>
  }
}

用import引入其他文件的组件

跟index.js同级下创建一个App.js 这里举例上面的类方法创建组件

  • 引入react模块 因为React.Component来自react模块 也可以直接解构出Component {Component}
  • 用export default抛出组件
import React,{Component} from 'react'
export default class App extends Component {
  render() {
    return <h2>App {this.props.name}</h2>
  }
}

在index.js下引入app模块

import App from './App' 

然后可以直接render App组件

复用组件

什么是复用组件

    1. 将多个组件进行整合,例如调用两次及以上相同的组件
    1. 结构非常复杂时需要将组件拆分成小组件
    1. 会存在父子关系的数据传递

复用组件的使用

  1. 假设有个button按钮需要复用,写一个MyBtn组件 return一个button标签
  2. 在需要用到该复用组件的组件的jsx里用标签的方式使用复用组件
  3. MyBtn作为子组件如果想获取父组件的值,可以在父组件里的子组件标签中挂载属性
  4. 子组件用this.props.属性名获取属性值
import React,{Component} from 'react'
class MyBtn extends Component {
  render() {
    return (
      <div>
        <button>{this.props.title}</button>
      </div>
    );
  }
}


export default class App extends Component {
  render() {
    return (
      <div>
        <h2>App {this.props.name}</h2>
        <MyBtn title='提交'></MyBtn>
        <MyBtn title='修改'></MyBtn>
        <MyBtn title='删除'></MyBtn>
      </div>
    )
  }
}

如何在React项目中拆分组件

假如有个App组件里有个评论块(comment),comment里面有用户信息块(userinfo),userinfo里有用户名块(username),这样看上去App组件会非常的长,我们需要拆分组件以便后期维护。如下面代码

// APP=>Comment=>userinfo=>username
export default class App extends Component {
  //如果有constructor 一定有个props 且一定要super继承props属性
  constructor(props){
    super(props);
    this.user = {
      name:'qlq',
      content:'这是react组件',
      date:new Date().toLocaleTimeString()
    }
  }
  render() {
    return (
      <div>
        <div className="comment">
          <div className="userinfo">
            <div className="username">
              姓名:{this.user.name}
            </div>
          </div>
          <div className="comment-content">
            评论内容:{this.user.content}
          </div>
          <div className="conmment-date">
            评论时间:{this.user.date}
          </div>
        </div>
      </div>
    )
  }
}
  • 由大往小拆分 这里最大的div为comment 那么我们就创建一个Comment组件
  • 用this.props.user.属性名来获取需要的属性值
class Comment extends Component {
  render() {
    return (
      <div className="comment">
        <div className="userinfo">
          <div className="username">
            姓名:{this.props.user.name}
          </div>
        </div>
        <div className="comment-content">
          评论内容:{this.props.user.content}
        </div>
        <div className="conmment-date">
          评论时间:{this.props.user.date}
        </div>
      </div>
    );
  }
}
export default class App extends Component {
  constructor(props){
    super(props);
    this.user = {
      name:'qlq',
      content:'这是react组件',
      date:new Date().toLocaleTimeString()
    }
  }
  render() {
    return (
      <div>
        <Comment user={this.user}></Comment>
      </div>
    )
  }
}

按照这样的步骤继续拆解组件,到不能拆的情况为(真实情况不一定需要拆分到这么细致)

class MyBtn extends Component {
  render() {
    return (
      <div>
        <button>{this.props.title}</button>
      </div>
    );
  }
}

// APP=>Comment=>userinfo=>username
class CommentDate extends Component {
  render() {
    return (
      <div className="conmment-date">
        评论时间:{this.props.user.date}
      </div>
    );
  }
}

class CommentContent extends Component {
  render() {
    return (
      <div className="comment-content">
        评论内容:{this.props.content}
      </div>
    );
  }
}

class Username extends Component {
  render() {
    return (
      <div className="username">
        姓名:{this.props.name}
      </div>
    );
  }
}

class UserInfo extends Component {
  render() {
    return (
      <div className="userinfo">
        <Username name={this.props.user.name}></Username>
      </div>
    );
  }
}

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        <UserInfo user={this.props.user}></UserInfo>
        <CommentContent {...this.props.user}></CommentContent>
        <CommentDate user={this.props.user}></CommentDate>
      </div>
    );
  }
}

export default class App extends Component {
  constructor(props){
    super(props);
    this.user = {
      name:'qlq',
      content:'这是react组件',
      date:new Date().toLocaleTimeString()
    }
  }
  render() {
    return (
      <div>
        <Comment user={this.user}></Comment>
      </div>
    )
  }
}

两个传值的技巧(具体详情在上一段代码已经体现)

  1. user={this.props.user} 假如子组件要的是user.name 子组件里调用就需要写{this.props.user.name}

    可以在父组件写成name={this.props.user.name} 这样在子组件就可以直接用{this.props.name}

  2. 同样的例子 可以用解构赋值 {...this.props.user} 这样在子组件也可以直接使用{this.props.name}

ps:第二个技巧比较好使

组件的样式

拿上面的Comment组件举例 如果要为name为commet的div添加样式可以创建一个样式文件 例:App.css

.comment{
  width: 500px;
  height:300px;
  border: 1px solid #eee;
}

然后在App.js引入样式文件

import './App.css'

引入外部图片也是同样操作

将图片放在目录下,然后用imoort xxx form '路径' 即可使用xxx

父子组件传值

React传值原则:单向数据流原则,即不能修改传递的props里的值

父传子

在拆分组件那部分中已经详细写了 父组件如何传递值给子组件

小结:在父组件中的子组件标签中定义要传的值,子组件通过this.props.属性名 获取属性值

子传父

  1. 父组件定义一个方法来处理子组件要传的值( add() )
  2. 在父组件的子组件标签里定义让子组件传值的方法 ( add={this.add} )
  3. 子组件定义一个事件绑定函数( handleClick = ()=>{} ) 函数中用this.props.add('要传递的值'),传递值.
  4. 子组件中的jsx有一个事件绑定函数 如onClick={this.handleClick}

ps:子组件中的事件绑定函数一定要注意this指向,利用箭头函数this指向上下文对象来指向子组件,如果用正常函数会报错

示例:App组件中拿到Comment组件的值

class Comment extends Component {
  // 用箭头函数将this指向Comment 不然是undefined
  handleClick = () => {
    this.props.add('这是子组件中的值');
  }
  render() {
    return (
      <div className="comment">
        <button onClick={this.handleClick} >子传父</button>
      </div>
    );
  }
}

export default class App extends Component {
  constructor(props) {
    super(props);
  }
  add(val) {
    alert(val);
  }
  render() {
    return (
      <div>
        <Comment user={this.user} add={this.add}></Comment>
      </div>
    )
  }
}

当点击button按钮时会alert子组件传过去的值

React中的状态state以及修改this指向的方法

如果组件渲染完成,想再修改视图中的数据就修改状态,让组件从新渲染

除了constructor之外的其他地方,修改状态的唯一方法是调用this.setState()

import React, { Component } from 'react'
export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      count:0
    }
  }
  add(){
    //修改状态的唯一方法是调用this.setState()
    this.setState({
      count:this.state.count+1
    })
  }
  render() {
    return (
      <div>
        <h2>{this.state.count}</h2>
        <button onClick={this.add}>点我</button>
      </div>
    )
  }
}

但是这会报错 因为add中的this指向指的是button标签,而setState方法是在App组件中,所以这时候需要修改this指向

修改this指向

1. 在constructor里用bind函数将this指向(此时是App)传递给this.add

  constructor(props){
    ...
    //改变this的指向 此时的this指向App,传给add,所以在add中的this指向的就是App,不然this为undefined
    this.add = this.add.bind(this)
  }

2.在标签中事件绑定的函数直接用this.add.bind(this)

<button onClick={this.add.bind(this)}>+1</button>

3.在绑定事件的函数中用箭头函数(利用箭头函数this指向,原理跟子传父中子组件的this指向一样)

add = ()=> {
      this.setState({
      count:this.state.count+1
    })
}

4. 在标签中使用箭头函数

<button onClick={(e)=>this.add(e)}>点我</button>

这种方法还可以在add(e)来接收事件对象

setState()方法的使用

setState是一个异步操作 在同一个次方法中拿到的是操作之前的数据,拿不到操作之后的数据。可以用函数的形式来写

this.setState((prevState,prevProps)=>({操作的对象}),()=>(对操作后的对象进行操作)

prevState :之前的状态

prevProps :之前的属性

export default class App extends Component {
  constructor(props){
    super(props);
    this.state = {count:0}
  }
  add(e){
    this.setState((prevState,prevProps)=>({
      count: prevState.count + 1 
    }),()=>{
      //是一个回调函数,在状态改变之后执行
      console.log(this.state.count);
    })}
  render() {
    return (
      <div>
        <h2>{this.state.count}</h2>
        <button onClick={(e)=>this.add(e)}>点我</button>
      </div>
    )
  }}

React生命周期

1.static defaultProps、constructor(props){super();this.state} 初始化 加载默认的状态

2.componentWillMount() 父组件将要被挂载

3.render() 父组件render了

4.componentDidMount() 父组件挂载完成

当前方法中,发起ajax请求,获取数据 数据驱动视图

5.shouldComponentUpdate(nextProps, nextState) 组件是否要更新

更新后的属性、状态

性能优化点 重要

retrun true 则更新
return false 则不更新

6.componentWillUpdate() 组件将要更新

7.render() 父组件渲染

8.componentDidUpdate() 组件已经更新完成

9.componentWillUnmount() 组件卸载完成

如果有子组件

componentWillReceiveProps(newProps) 子组件将要接受新属性

受控组件的实现

受控组件:受状态控制的组件,需要与状态进行相应的绑定
例子:input输入框一个值,点击提交按钮,会在列表中生成对应的值

1. 设置默认值

在constructor中的this.state设置默认值

  constructor(props){
    super(props);
    this.state = {
      val: '', //输入框的值
      data: [] //表单要渲染的值
    }
  }

2. 标签中绑定值

input标签中绑定val

3. 用(onChange)事件对状态进行一个修改

绑定onChange方法

render() {
    return (
      <div>
        <input type="text" val={this.state.val} onChange={this.handleChange}/>
        <button onClick={this.handleClick}>提交val</button>
        <ul>
          {
            this.state.data && this.state.data.map((item,index)=>{
              return (
                <li key={index}>{item}</li>
              )
            })
          }
        </ul>
      </div>
    )
  }

4. onChange方法内部用this.setState改变默认值val

一.可以用e.target.value获取输入框中的值

  handleChange = (e)=>{
    let val = e.target.value;
    this.setState({
      val:val,
    })}

二.也可以用ref来获取

  1. input标签中添加 ref = 'a'
  2. 在handleChange方法中 用this.refs.a.value 即可获取输入框中的值
  handleChange = (e)=>{
    // let val = e.target.value;

    let val = this.refs.a.value;
    this.setState({
      val:val,
    })}

5. handleClick方法内部表单更新方法

一. 判断输入框内是否有值(this.state.val)

二. 创建一个新数组用...this.state.data拿到之前数据

三. 新数组push(this.state.val)

四. 用this.setState重新更改状态里的data值

  handleClick=()=>{
    if(this.state.val){
      let newArr = [...this.state.data];
      newArr.push(this.state.val);
      this.setState({
        data:newArr
      })
    }}

表单的使用

import React, { Component } from 'react';
class FormSimple extends Component {
  constructor(props){
    super(props);
    this.state ={
      username:'',
      password:'',
      selectArr:''
    }
  }
  handleChange = (e)=>{
    this.setState({
      username:e.target.value
    })
  }
  handlePwd = (e) => {
    this.setState({
       password: e.target.value
    })
  }
  handleSubmit = (e)=>{
    //阻止默认事件
    e.preventDefault();
    if(this.state.username && this.state.password && this.state.selectArr){
      //发送ajax请求
      alert(`当前用户名:${this.state.username} 当前用户密码:${this.state.password} 当前爱好:${this.state.selectArr}`)
    }else{
      alert(`请填完整信息`);
    }
  }
  handleSelect = (e) =>{
    let newArr = [...this.state.selectArr];
    newArr.push(e.target.value);
    this.setState({
      selectArr:newArr
    })
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <p className="username">
          <label htmlFor="name">用户名:</label>
          <input type="text" value={this.state.username} onChange={this.handleChange} id='name'/>
        </p>
        <p className="password">
          <label htmlFor="pwd">密码:</label>
          <input type="password" value={this.state.password} onChange={this.handlePwd} id='pwd'/>
        </p>
        我的爱好:
        <select value={this.state.selectArr} onChange={this.handleSelect}>
          <option value="抽烟">抽烟</option>
          <option value="喝酒">喝酒</option>
          <option value="烫头">烫头</option>
        </select>
        <br/>
        <input type="submit" value="登录" />
      </form>
    );
  }
}

export default FormSimple;