从零开始学React(上)

从零开始一个前端项目

安装部分

  • (如果未安装create-react-app)npm install -g create-react-app
  • 在对应文件夹下create-react-app 文件名创建对应的项目
  • 失败大多因为powershell权限问题,百度搜索,易于解决

脚手架文件夹浅析

一个App.js文件其实就是一个组件(HTML+CSS+JS),在App.js中
(注意,jsx语法中,组件首字母必须大写)
这个App.js也可以重命名,比如Todolist.js,如果修改,要记得修改本文件中的class Todolist。还得修改index.js中的import Todolist from “./Todolist”、ReactDom.render()

import React,{component} from "react";
class App extends Component{
	render(){
		return(
			<div>你好</div>//在js文件中使用HTML标签,是JSX语法使其生效
			//另外jsx要求return的必须全部包裹在一个<div>中
			//或者import React,{component,Fragment} from "react"
			//然后将所有东西包裹在<Fragment>标签中
		);	
	}
}
ES6的解构赋值,其实等价于:
import React from "react";
const Component = React.componet;

上面这是旧版的写法,新版18.0已经不支持ReactDom.render,但旧版仍能生效,新版是下面样子

function App() {
  return (
    <div>
      hello world
    </div>
  );
}

export default App;

基础JSX语法使用

  • 在render中的标签中如果想使用js语句,需要加{ }
  • 调用函数的话,只写函数名,不要带上括号,{this.functionName}
  • React中标签绑定事件,如onclick、onchange等,第二个字母要大写
  • 改变组件的状态,要用setState({ … }),不允许直接修改state内的数据:this.state.inputvalue=“改变”,这种就不行,不能直接修改,可以拷贝一个副本
  • JSX中首字母大写的一般都是组件
  • JSX的注释:{/*这是注释*/ },就相当于在里面使用js,需要加{ }
import React, { Component } from "react";
class Todolist extends Component {
  constructor(props) {
    super(props);//用于继承Component类的一些属性
    this.state = {
      inputValue: "请输入内容"  
    }
  }
  render() {
    return (
      <div>
        <div>
          <input
            value={this.state.inputValue}
            onChange={this.handle.bind(this)}>//这里bind返回的是一个函数,并未调用
          </input>
          <button>提交</button>
        </div>
        <ul>
          <li>学前端</li>
          <li>应付科研</li>
          <li>好好休息</li>
        </ul>
      </div>
    );
  }
  handle(e) {
    console.log(this);//显示是Todolist
    // console.log(e.target.value);
    this.setState({
      inputValue: e.target.value
    })  
  }
}
export default Todolist;

实现一个基本的todolist,可以增删

这是Todolist.js文件(App.js)
import React, { Component } from "react";
class Todolist extends Component {
  constructor(props) {
    super(props);//用于继承Component类的一些属性
    this.state = {
      inputValue: "请输入内容",
      list: ["学前端", "搞科研","运动锻炼"]
    }
  }
  render() {
    return (
      <div>
        <div>
          <input
            value={this.state.inputValue}
            onChange={this.handle.bind(this)}>
          </input>
          <button onClick={ this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {this.state.list.map((item,index) => {
            return <li key={index} onClick={ this.handleItemDelete.bind(this,index)}>{item}</li>
          }) }
        </ul>
      </div>
    );
  }
  handle(e) {
    // console.log(this);
    // console.log(e.target.value);
    this.setState({
      inputValue: e.target.value
    })  
  }
  handleBtnClick() {
    this.setState({
      list: [...this.state.list, this.state.inputValue],
      inputValue:""
    })
  }
  handleItemDelete(k) {
    let newlist = this.state.list;
    newlist.splice(k, 1);//删除一个元素
    this.setState({
      list:newlist
    })
  }
}
export default Todolist;

jsx语法补充

  • label的for要改为htmlFor(以防与循环冲突)
  • 标签样式名不能用class,要用className,以免与类冲突
  • 如果想要对输入框内的文字,取消自动转义,可以用dangerouslySetHTML(有遭受XSS攻击的风险)

组件拆分

拆分上面的Todolist.js文件,具体是把它里面的<li>标签剥离出来,形成一个TodoItem.js的文件,而后再进行父子组件通信。
新建一个TodoItem.js,并且在Todolist.js中import引入
将Todolist.js中的下面这一部分

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

改成下面的样子:就能获取到父组件的数据了

    <ul>
      {this.state.list.map((item,index) => {
        return <TodoItem content={ item }/>
      }) }
    </ul>
对应的TodoItem.js内的初步写法如下
class TodoItem extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <li>{ this.props.content}</li>
        );
    }
}
  • 父传子:通过属性props,父组件不仅可以传值还可以传方法给子组件
    接下来想实现点击删除的操作,可以将父组件的 handleItemDelete(k)传给子组件,让子组件来调用
     <ul>
       {this.state.list.map((item,index) => {
         return <TodoItem content={item} in={index}
           dadDelete={ this.handleItemDelete.bind(this)}
           {/*如果这里dadDelete={ this.handleItemDelete},就会报错,原因是
           在TodoItem.jsz中无法找到this.handleItemDelete这个函数,因此只有绑定了
           this值,子组件在编译到这句话时,才能正确调用父组件的函数*/}
               />
         })
       }
     </ul>
 对应的TodoItem.js内的写法如下
 class TodoItem extends React.Component {
    constructor(props) {
        super(props);
        this.deleteFu = this.deleteFu.bind(this);//放在这里会节约性能
    }
    render() {
        return (
            <li onClick={this.deleteFu}>{ this.props.content}</li>
        );
    }
    deleteFu() {
        // console.log(this.props.in);
        this.props.dadDelete(this.props.in);
    }
}

单向数据流

react只允许单向的。比如子组件只能去使用来自父组件的值,但不能去改变它

代码优化

  • 使用解构赋值
  • 将标签内的函数this.deleteFu.bind(this)全部放在constructor内:this.deleteFu = this.deleteFu.bind(this)
  • setState()括号内由返回对象改为返回箭头函数(箭头函数内返回对象)
  • setState()内凡是用到this.state的地方都可以通过箭头函数传入prevState来代替
优化后总体的TodoItem.js如下:
import React from "react";
class TodoItem extends React.Component {
    constructor(props) {
        super(props);
        this.deleteFu = this.deleteFu.bind(this);//放在这里会节约性能
    }
	render() {
        const { content } = this.props;{/*解构赋值*/}
        return (
            <li onClick={this.deleteFu}>{content}</li>{//}
            <li onClick={this.deleteFu}>{ this.props.content}</li> {/旧,弃用/}
        );
    }
    deleteFu() {
        // console.log(this.props.in);
        const { dadDelete,index } = this.props;{/*注意这里函数也解构赋值了*/}
        dadDelete( index );
    }
}
export default TodoItem;

setState()的优化步骤如下面所示,由新到旧

  handleBtnClick() {
    /*版本3.0最终版 */ /*()=>{return{}}改进为()=>({}) */
    this.setState((prevState) => ({
        list: [...prevState.list, prevState.inputValue],
        inputValue: ""
      }
    ))
    /*版本2.0 */ /*由对象改为箭头函数 */
    // this.setState(() => {
    //   return {
    //     list: [...this.state.list, this.state.inputValue],
    //     inputValue: ""
    //   }
    // })
    /*版本1.0*/
    // this.setState({
    //   list: [...this.state.list, this.state.inputValue],
    //   inputValue:""
    // })
  }
  handleItemDelete(k) {
    /*版本2.0 */
    this.setState((prevState) => {
      const newlist = prevState.list;
      newlist.splice(k, 1);
      return {
        list: newlist
      }
    })
    /*版本1.0 */
    // let newlist = this.state.list;
    // newlist.splice(k, 1);//删除一个元素
    // this.setState({
    //   list:newlist
    // })
  }

每当数据或者props发生改变的时候,就会重新render

父组件render,子组件也会重新render,因为子组件就嵌在父组件内

React虚拟DOM

  • state数据
  • JSX模板
  • 数据+模板 生成虚拟DOM(用JS实现),比如
    [”div“, {id:“name”},[”span“,{ },“hello world”] ]
    React提供了一些API来创建一般JS对象:
    const vDOM=React.createElement(‘xx’,{id:‘xx’},‘xx’)
  • 用虚拟DOM生成真实DOM
    <div id=“name”><span>hello world</span></div>
  • state数据改变
  • 数据+模板 生成新的虚拟DOM
    [”div“, {id:“name”},[”span“,{ },“you will get what you want” ] ]
  • 比较新的虚拟DOM与原始虚拟DOM的区别
    Diff算法:二叉树同层比对,带key和不带key的比对,key要唯一才好
  • 根据刚才找到的差异,直接操作DOM,改变span的内容

优点:

  • 性能得到了提升,不需要数据一改变就重新渲染整个DOM
  • 使得跨端应用得以实现,React Native
  • React高效的原因就是Diff算法!

setState()是异步操作

  • setState(()=>{},()=>{})其实里面是可以放两个函数的,第一个是异步更改状态,第二个是更改完成之后的callback函数,大多数时候并不需要使用callback
  • setState()底层有其优化机制,如果短时间内多次更改状态,他会把几次的状态改变集中在一起观察比较,这样避免了多次生成VDOM

ref提供了一种直接获取DOM的方法,但最好别用

<label htmlFor="content">请输入内容
  <input
    id="content"
    value={this.state.inputValue}
    onChange={this.handle}
    ref={(input) => {this.input=input }}>{/*这里就提供了可以直接拿到input节点的方式*/}
  </input>
</label>
在之后的方法中,就可以不用事件e.target
handle(e) {
   // const value = e.target.value;不用ref,最好采用这种方式
   const value = this.input.value;//用ref可以直接获取input节点,最好别用
   this.setState(() => {
     return {
     inputValue: value
     }
   })
 }

组件的生命周期函数(旧)

初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render()
  4. componentDidUpdate()

卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()
    React生命周期旧图

组件的生命周期函数(新)

初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()
  2. getDerivedStateFromProps
  3. render()
  4. componentDidMount()

更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. getDerivedStateFromProps
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate()

卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()
    React生命周期新图
    弃用了三个will函数

组件的生命周期演示:

class TodoItem extends React.Component {
    constructor(props) {
        super(props);
        this.deleteFu = this.deleteFu.bind(this);//放在这里会节约性能
    }
    componentWillMount() {/*已经不鼓励用了,最好别用 */
        console.log("componentWillMount");
    }
    shouldComponentUpdate() {
        console.log("shouldComponentUpdate?");
        return true;
    }
    render() {
        console.log("render");
        const { content } = this.props;
        return (
            <li onClick={this.deleteFu}>
                {content}</li>
        );
    }
    deleteFu() {
        // console.log(this.props.in);
        const { dadDelete,index } = this.props;
        dadDelete( index );
    }
    componentDidMount() {/*适合发送AJAX请求 */
        console.log("componentDidMount");
    }
    componentWillUnmount() {/*适合收尾工作,比如清理定时器,这个函数并未弃用,而且还是比较重要的勾子函数 */
        console.log("componentWillUnmount");
    }
}

生命周期函数的应用

  • 比如shouldComponentUpdate()来看是否该更新,以提升性能
  shouldComponentUpdate(nextProps,nextState) {
       console.log("shouldComponentUpdate?");
       if (nextProps.content !== this.props.content) {
           return true;
       } else {
           return false;
       }
   }

效果图
效果如上,可见子组件TodoItem.js在输入框输入的同时,一直在打印shouldComponentUpdate?可在”提交“按钮未被点击之前并没有child render,这样就优化了性能

  • componentDidMount()函数中发送AJAX请求
    需要先引入依赖axios:终端输入npm install axios --save
    就可以在node_modules里面看到axios了
    在组件头部import axios from “axios”;
    开始敲
   componentDidMount() {/*适合发送AJAX请求 */
         axios.get(".api/somedata")//这个文件不存在,所以一定会报错
           .then((res) => { console.log(res) })
           .catch((err) => { alert(err) });
   }

失败

axios实现服务器客户端通信

两个不同的端口不能进行通信的根源在于AJAX引擎的阻拦,他不允许协议、端口、主机(域名)不同的两台设备进行通信,可以返回请求数据,但拿不到。中间的代理服务器之所以能作为服务器和客户端的中间人,是因为他没有AJAX引擎,因此不受协议、端口、主机约束。
如果想使用代理,可以在package.json里面添加”proxy“:"http://…地址"但这种方式只能转发给一台固定的服务器。不能转发给多个地址。
更有效的方法,是在src里面新增一个setupProxy.js的文件,它是用commonJS写的,这个文件是固定的名称,不能改名,React脚手架会找到这个文件,把它加到webpack的配置里面
创建一个文件夹,编写自创的服务器脚本myCreateServer.js

const express = require('express')
const app = express()
//这个是用express语言写的
app.use((request, response, next) => {
    console.log('有人请求服务器');
    console.log('请求来自于', request.get('Host'));
    console.log('请求的地址', request.url);
    next()
})

app.get('/todos', (request, response) => {
    const todos = [
        "学前端","爱惜身体","搞科研"
    ]
    response.send(todos)
})

app.listen(5000, (err) => {
    if (!err) console.log('服务器启动成功,服务器地址为:http://localhost:5000/todos');
})

需要在这个文件中npm install require、npm install express这两个包才能启用服务器:node myCreateServer.js,服务器启动
在Todolist.jsx文件夹中编写setupProxy.js文件,这个文件是用commonJs写的,用到了http-proxy-middleware中间件,这是create-react-app自动下载的,旧版的写法已经不支持了,改成下面的写法即可

const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
module.exports = function (app) {
    app.use(
        proxy('/api', { //遇见/api1前缀的请求,就会触发该代理配置
            target: 'http://localhost:5000', //请求转发给谁
            changeOrigin: true,//控制服务器收到的请求头中Host的值,欺骗服务器让它以为请求一样来自5000端口
            pathRewrite: { '^/api': '' } //重写请求路径(必须)
        })
    );
}

在componentDidMount()函数中发送AJAX请求,并将请求返回得到的数据加以利用

  componentDidMount=()=> {/*适合发送AJAX请求 */
    console.log("componentDidMount");
    axios.get("http://localhost:3000/api/todos")
      .then((res) => {
        console.log(res.data);
        this.setState(() => {
          return {
            list: res.data
          }
        })
      })
      .catch((err) => { alert(err); })
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值