React从零基础入门到实战

372 阅读13分钟

第二章: React初探

本章主要讲解React项目的开发环境搭建,工程代码结构及React中最基础的语法内容,同时对前端组件化思想进行介绍。

2-1 React简介

Facebook推出

2013年开源

函数式编程

使用人数最多的前端框架

健全的文档与完善的社区

React官网: reactjs.org/

把16以后的reacr叫做Reacter

2-2 React开发环境准备

  1. 引入.js文件来使用React
  2. 通过脚手架工具来编码
  3. react脚手架工具 Create-react-app

打开网址: reactjs.org/docs/create…

下面有两种方式搭建react脚手架

使用npm搭建React脚手架

npm install -g create-react-app
create-react-app my-app
yarn start


cd my-app
npm start

使用npx搭建React脚手架

npx create-react-app my-app
cd my-app
npm start

然后就可以看见你项目的启动界面了

2-4 React中的组件

import React, {component} from 'react'
// 其中的import {component} from 'react'
//等价于
//import React import 'react'
//const Component = React.Component

class App extends Component {
    render() {
        return (
            <div>
                hello,dell lee
            </div>
        );
    }
}

export default App;




//或者使用下面的方式实现组件化开发
//现在新版的React都使用下述的方法实现函数
function App() {
    return (
        <div>
            hello,andy
        </div>
    );
}

export default App;


在src文件夹下的index.js文件中

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

意思就是说将我们的App内容挂载到public/index.html文件的id="root"的div节点上

// public/index.html
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

2-5 最基础的JSX语法

我们在index.js中引用的App就是自己定义的标签,而在App.js中使用的div标签也是JSX语法

第三章:Reacr基础精讲

3-1 使用React编写Todolist功能

react在使用函数的时候,一个函数的所有的功能最外层必须是一个元素包裹,就是说一个函数的最外层只能有一个层级标签

function TodoList() {
    return (
        <div >
            <div><input type="text"/>
                <button>提交</button>
            </div>
            <ul>
                <li>学英语</li>
                <li>learn English</li>
            </ul>
        </div>
    );
}

export default TodoList;

但是有的时候我们不想让这个最外层的标签显示,这样的话就是用react中的Fragment代替最外层的div标签

3-2:React中的响应式设计思想和事件绑定

 // 两种数据存储
// 一种存储iinput框中的值
// 一种存储显示的列表中的值

现在实现一个输入框,键盘输入什么内容,输入框显示什么内容

首先上面新建了一个TodoList.js的文件,在index.html中已经将TodoList.js挂载到了toor节点上,所以下述的内容皆是TodoList中的内容

1 我们在TodoList类中写一个输入框和提交按钮

import React, {Component, Fragment} from 'react';

class TodoList extends Componebt {
    reder() {
        return (
        	<Fragment>
        		<div>
                	<input />
                    <button>提交</button>
                </div>
                
                 <ul>
                    <li>学英语</li>
                    <li>learn English</li>
                </ul>
            </Fragment>
        )
    }
}

2 我们使用constructor实现构造函数来继承类的属性

将输入框中显示的值使用一个变量代替,将输入框的值绑定到变量上,在里面实现一个事件,只要输入框中有值,立马就改变输入框的值,我们就可以看见只要是自己输入有数值,就会在输入框中显示出来

import React, {Component, Fragment} from 'react';

class TodoList extends Componebt {
    
    constructor(props) {
        super(props);

        //组件的状态
        this.state = {
            inputValue: "hello ",
            list: []
        }

    }
    
   render() {
        return (
            <Fragment>
                <div>
                    {/*为了让输入框的值只想组件状态的inputValue*/}
                    <input
                        type="text"
                        value={this.state.inputValue}
                        // 使用bind改变方法的this指向
                        onChange={this.handleInputChange.bind(this)}
                    />
                    <button>提交</button>
                </div>
                <ul>
                    <li>学英语</li>
                    <li>learn English</li>
                </ul>
            </Fragment>
        )
    }
    // 输入框中的事件
     handleInputChange(e) {
        // target对应着input的Dom节点

        // console.log(e.target.value);
        // console.log(this);

        // 不改变this指向的时候,undefined,
        // 在调用handleInputChange方法的时候加上.bind(this),就改变了this指向,
        // 在本方法中就可以调用其他方法的属性了

        // 我们不能直接使用下面的方法来直接改变input框里面的值
        // this.state.inputValue=e.target.value;

        // 想要改变state的值,必须使用this.setstate({})
        this.setState(
            {
                inputValue: e.target.value
            }
        )
    }
    
}

3-3: 实现TodoList新增删除功能

import React, {Component, Fragment} from 'react';

class TodoList extends Component {
    // 构造函数,super是继承父类的构造函数
    constructor(props) {
        super(props);

        //组件的状态
        this.state = {
            inputValue: "",
            list: ["玩游戏", "吃雪糕"]
        }

    }

    render() {
        return (
            <Fragment>
                <div>
                    {/*为了让输入框的值只想组件状态的inputValue*/}
                    <input
                        type="text"
                        value={this.state.inputValue}
                        // 使用bind改变方法的this指向
                        onChange={this.handleInputChange.bind(this)}
                    />
                    {/*为提交按钮绑定一个点击事件*/}
                    <button onClick={this.handleBtnClick.bind(this)}>提交</button>
                </div>
                <ul>
                    {
                        // 这一段代码的意思就是使用map循环数组的每一项,获取每一项的值item和索引index
                        // 然后进行回调,返回li标签,并包含item,也就是每一项的值
                        this.state.list.map((item, index) => {
                            return (
                                <li key={index}
                                    onClick={this.HandleItemDelete.bind(this,index)}>{item}</li>
                            )
                        })
                    }
                </ul>
            </Fragment>
        )
    }

    // 改变输入框的值得事件
    handleInputChange(e) {
        // target对应着input的Dom节点

        // console.log(e.target.value);
        // console.log(this);

        // 不改变this指向的时候,undefined,
        // 在调用handleInputChange方法的时候加上.bind(this),就改变了this指向,
        // 在本方法中就可以调用其他方法的属性了

        // 我们不能直接使用下面的方法来直接改变input框里面的值
        // this.state.inputValue=e.target.value;

        // 想要改变state的值,必须使用this.setstate({})
        this.setState(
            {
                inputValue: e.target.value
            }
        )
    }

    // 点击事件
    handleBtnClick() {
        this.setState({
            // [...,this.state.list]就是一个展开运算符,就是将之前list中的数据全部展示,
            // 然后后面跟上要添加的数据,然后又将结果辅助给list,即实现列表添加新元素
            list: [...this.state.list, this.state.inputValue],
            inputValue: ""  // 实现input框清空
        })
    }

    // li标签的删除操作
    HandleItemDelete(index) {
        // immutable
        // state 不允许我们做任何的改变


        // 拷贝list
       const list=[...this.state.list];
        list.splice(index,1);
        this.setState({
            list:list
        });
    }
}

export default TodoList;


自己实现一个TodoList实例

import React,{Component,Fragment} from 'react'

class TodoList extends Component {
    constructor(props) {
        super(props);
        // 定义一个组件
        this.state={
            inputValue:"",
            list:["张三","李四","王五"]
        };
    }


    render(){
        return (
            <Fragment>
                <div>
                    <input type="text" value={this.state.inputValue} onChange={this.HandleInputChange.bind(this)}/>
                    <button onClick={this.HandleBtnClick.bind(this)}>提交</button>
                </div>
                <ul>
                    {
                        this.state.list.map((item,index) => {
                            return <li key={index} onClick={this.HandleLiClick.bind(this,index)}>{item}</li>
                        })
                    }
                </ul>
            </Fragment>
        )
    }
    // 实现输入框输入的时候显示文本
    HandleInputChange(e){
        this.setState({inputValue:e.target.value})
    }
    // 实现提交
    HandleBtnClick(){
        this.setState(
            {
                list:[...this.state.list,this.state.inputValue],
                inputValue:""

            }
            )

    }

    // 点击li实现删除
    HandleLiClick(index){
        const list=[...this.state.list];
        list.splice(index,1);
        this.setState({
            list:list
        });

    }
}

export default TodoList;

3-4:JSX语法细节补充

1 以大写字母开头的标签是组件,以小写字母开头的元素就是普通元
2 React的注释{/**/},//,{//}
3 在我们使用class来定位标签添加css样式的时候,会有警告,应为react里面class会被识别成类,所以使用className代替class

实现点击输入框钱文本,光标定位到输入框

在input中添加id="insertArea",然后在label中添加htmlFor="insertArea"

image-20200907012345129

3-5 拆分组件和组件之间的传值

父组件调用子组件

// 在父组件中
import TodoItem from "./TodoItem";
 <div>
{/*组件间的传值,直接在子组件后面写content={item},就可以在子组件里面调用content,获取item的值*/}
{/*传参的时候,直接将参数当做标签的属性传过去,也可以将父组件的方法传递过去,但是要绑定父组件的this*/}
<TodoItem content={item} index={index} deleteItem={this.handleItemDelete.bind(this)}/>
</div>


// 在子组件中,调用父组件传过来的参数
class TodoItem extends Component {
    constructor (props){
        super(props);
        this.handleClick = this.handleClick.bind(this)
    }

    render() {
        // 父组件传一个属性一个值content,使用this.props.content来获取参数
        return <div onClick={this.handleClick}>{this.props.content}</div>
    }

    // 子组件如何调用父组件的内容
    handleClick(){
        this.props.deleteItem(this.props.index)
    }
}

export default TodoItem;

3-6 TodoList代码优化

import React, {Component, Fragment} from 'react'
import "./style.css"
import TodoItem from "./TodoItem";

class TodoList extends Component {
    constructor(props) {
        super(props);
        // 定义一个组件
        this.state = {
            inputValue: "",
            list: ["张三", "李四", "王五"]
        };

        // 为了优化性能,我们把this的绑定写在构造函数中
        this.HandleInputChange = this.HandleInputChange.bind(this);
        this.HandleBtnClick = this.HandleBtnClick.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
    }


    render() {
        return (
            <Fragment>
                <div>
                    <label htmlFor="insertArea">输入内容</label>
                    <input id="insertArea" className="input" type="text" value={this.state.inputValue}
                           onChange={this.HandleInputChange}/>
                    <button onClick={this.HandleBtnClick}>提交</button>
                </div>
                <ul>
                    { this.getTodoItem() }
                </ul>
            </Fragment>
        )
    }

    // 为了代码更加精简,我们将ul里的代码放到一个方法中,只需要在上render中执行一个这个方法即可
    getTodoItem(){
        return this.state.list.map((item, index) => {
            return (
                <div>
                    {/*组件间的传值,直接在子组件后面写content={item},就可以在子组件里面调用content,获取item的值*/}
                    <TodoItem
                        key={index}
                        content={item}
                        index={index}
                        deleteItem={this.handleItemDelete}
                    />
                </div>
            )
        })
    }

    // 实现输入框输入的时候显示文本
    HandleInputChange(e) {
        // 异步的设置数据
        const value = e.target.value;
        this.setState( () => ({
                inputValue:value

        }));
    }

    // 实现提交
    HandleBtnClick() {
        // 在修改数据的时候prevState就是修改前的数据,这里的prevState=this.State
        this.setState( (prevState)=> ({
            list: [...prevState.list, prevState.inputValue],
            inputValue: ""
        }));


    }

    // 点击li实现删除
    handleItemDelete(index) {
        this.setState(() => {
            const list = [...this.state.list];
            list.splice(index, 1);
            return {
                list
            }
        } );

    }
}

export default TodoList;


3-7:思考

image-20200915125924870

第四章:React高级内容

4-1:Reactdeveloptools的安装和使用

在chrome的扩展程序中输入react进行安装

4-2:PropTypes与DefaultProps

import React, {Component} from 'react'
// 这里导入propTypes为了实现参数的类型指定
import propTypes from 'prop-types';

class TodoItem extends Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this)
    }

    render() {
        const {content, text} = this.props;
        // 父组件传一个属性一个值content,使用this.props.content来获取参数
        return <div onClick={this.handleClick}>{text}-{content}</div>
    }

    // 子组件如何调用父组件的内容
    handleClick() {
        const {deleteItem, index} = this.props;
        deleteItem(index)
    }
}

// React中的类型指定
TodoItem.propTypes = {
    text: propTypes.string.isRequired,  // 指定一个参数是必须传的
    content: propTypes.string,  // 指定content参数是一个字符串
    deleteItem: propTypes.func,  // 指定deleteItem参数是一个函数
    index: propTypes.number,  // 指定index参数是一个数字
};

// 子组件的参数指定默认值,父组件没有传值也可以指定默认值
TodoItem.defaultProps = {
    text: "hello"
};

// 更多的propTypes和DefaultProps的更多语法
// https://reactjs.org/docs/typechecking-with-proptypes.html

export default TodoItem;

4-3 :props,State与render函数

// 当我们在输入框中输入文本的时候看到的现象,是因为input的value被操作
// 当组件中的state或者props发生改变的时候,render函数就会重新执行
// 当父组件的render函数被运行的时候,子组件的render都将会被重新运行
import React, {Component} from 'react'

class Test extends Component {
    // 当父组件的render函数被运行的时候,子组件的render都将会被重新运行
    render() {
        console.log("Test render");
        return <div>{this.props.content}</div>

    }
}

export default Test;

4-4:React中的虚拟DOM

在程序运行之前,就是在数据渲染之前将标签赋值到虚拟DOm中,在下次数据变更之前将现在的DOM和虚拟Dom做对比,进行局部刷新

4-7 React中ref的使用

使用ref操作DOM

<input
    ref = {(input) => {this.input=input}}
    />

// 使用ref就是说让this.input指定input标签的真实的DOM节点

// 比如在我们的点击事件中,把之前传进来的e换成this.input
    // 实现输入框输入的时候显示文本
    HandleInputChange() {
        // 异步的设置数据
        const value = this.input.value;
        this.setState( () => ({
                inputValue:value

        }));
    }

// 在ul标签内实现ref
 <ul ref={(ul) => {this.ul=ul}}>
 	{ this.getTodoItem() }
 </ul>

// 在ul中实现打印div标签的长度
    // 实现提交
HandleBtnClick() {
    // 在修改数据的时候prevState就是修改前的数据,这里的prevState=this.State
    this.setState( (prevState)=> ({
        list: [...prevState.list, prevState.inputValue],
        inputValue: ""
    }));

    // 获取ul中的div标签的长度
    console.log(this.ul.querySelectorAll('div'.length))
}



4-8:React的生命周期函数

// 生命周期函数指的是在某一时刻组件会自动调用执行的函数
render就是一个生命周期函数,因为随着State和props的变化,render会被执行

初始化-->挂载-->

声明周期说明

initialization: // 是一个初始化函数
Mountiong: // 组件第一次挂载到页面的流程
	componentWillMount: // 是在组件即将被挂载到页面的时刻自动执行
    render: // 负责页面重新渲染的
    componentDidMount: // 组件被挂载到页面之后,自动被执行
Updation: // 组件的更新,数据发生变化的时候,页面的更新会被执行
	shouldComponentUpdate: // 组件被更新之前,他会自动被执行
	// 如果shouldComponentUpdate返回的是True,他才会被执行
	// 如果返回False,componentWillUpdate就不会被执行
	componentWillUpdate: // 组件被更新之前,他会自动执行,但是他在shoudComponentUpdate之后
	render: // 数据发生变化,虚拟DOm发生改变,真实的Dom发生改变,我们的页面的数据就会发生变化
	ComponentDidUpdate: // 组件更新完之后,该组件会被执行
	// 当一个组件ongoing父组件接受了参数
	// 只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被执行
	// 如果这个组件第一次存在于父组件中,不会执行
	// 如果这个组件之前已经存在于父组件中,才会执行
	componentWillReceivePros:  
	componentWillUnmount: // 当这个组件即将从页面中剔除就会被执行


	

先说一些shoudComponentUpdate,返回值就是True或者False,他是一个"门"的角色,"中间件"的角色,来过滤来去的数据请求

4-9: React生命周期函数的使用场景

所有的生命周期起函数都可以不写,但是render不能不写

    // 父組件已更新,子組件就更新了,損耗了性能,为了避免性能的损耗,就要在子组件中使用shouldComponentUpdate
    shouldComponentUpdate(nextProps, nextState) {
        if (nextProps.content !== this.props.content) {
            return true;
        } else {
            return false;
        }
    }

ajax位置

我们在reder中每一次有数据变化,render函数就会被执行,加入render中有ajax请求,那么我们没输入一个值,ajax就会被执行,这是不合理的

// 我们可以将AjAx请求放到ComponentDidMount中,因为componentDidMount只会在组件挂载到页面上的时候执行一次
// 同时也可以将ajax请求放到constructor方法中,也是没有问题的,因为constructor也只是组件初始化的时候被执行一次
但是推荐将ajax放到componentDidMount中

由于React没有juqury那样内置有ajax模块,只能通过第三方的模块实现功能-----axios

// 安装yarn
npm install -g yarn

// 在项目目录下
yarn add axios

React模拟ajax进行数据请求

// TodoItem.js文件中
import axios from 'axios'


// 在componentDidMount中实现数据请求
componentDidMount() {
    axios.get('/api/todolist')
    .then(() => {alert("successful")})
    .catch (() => {alert("error")})
}

4-10 使用charles工具进行接口数据模拟

image-20200926232107452

使用charles工具模拟接口请求的时候,首先创建一个json格式的文件,比如todolist.json

["欧阳","晓晖","Andy","Kate","张三","李四","王五"]

然后在按照上面图片的步骤实现相关设置

1 点击tools,点击Map local Settings
2 勾选Enable Map Local,点击Add
3 勾选协议,输入ip地址,端口,和请求的路径,选择刚刚的json文件,点击ok

ajax接收接口数据

    componentDidMount() {
        axios.get('/api/todolist')
            .then((res) => {
                console.log(res);
                this.setState(() => (
                    {list: [...res.data]}
                ))
            })
            .catch(() => {
                alert("error")
            })
    }

4.11 React中实现css过渡动画

src中只有App.js和index.js代码

实现

// index.js代码
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./App";

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


// App.js代码
import React, {Component, Fragment} from 'react'
import './style.css';

class App extends Component{

    constructor(props) {
        super(props);
        this.state={
            show:true
        };
        this.handleToggole = this.handleToggole.bind(this);

    }

    render() {
        return (
           <Fragment>
               <div className={this.state.show ? 'show':'hide'}>hello</div>
               <button onClick={this.handleToggole}>toggle</button>
           </Fragment>
        )
    }

    handleToggole(){
        this.setState({
            show:this.state.show ? false : true
        })
    }
}
export default App;

// style.css
.show{
    opacity: 1;  /* 显示 */
    transition: all 1s ease-in;  /* 过渡时间 */
}

.hide{
    opacity: 0;  /* 不显示 */
    transition: all 1s ease-in;   /* 过渡时间 */
}


4-12 React中使用CSS动画效果

// style.css
.show {
    animation: show-item 2s ease-in forwards;
    // 参数说明: show-item:执行动画的模块 2s:过渡时间 ease-in:曲线效果 forwards:保留最后一次动画的效果
}

.hide {
    animation: hide-item 2s ease-in forwards;
}

// 参数说明:keyframes:关键帧 opacity:透明度 color:颜色
@keyframes show-item {
    0% {
        opacity: 0;
        color: red;
    }

    50% {
        opacity: 0.5;
        color: green;
    }

    100% {
        opacity: 1;
        color: yellow;
    }

}

@keyframes hide-item {
    0% {
        opacity: 1;
        color: yellow;
    }

    50% {
        opacity: 0.5;
        color: green;
    }

    100% {
        opacity: 0;
        color: red;
    }
}


4-13 : 使用 react-transition-group实现动画

github.com/reactjs/rea…

Installation

# npm
npm install react-transition-group --save

# yarn
yarn add react-transition-group

Components

我们着重看CSSTransition

接下来是CSSTransition的一些用法

// App.js
import React,{ Fragment, Component } from 'react'
import { CSSTransition} from 'react-transition-group'  // 导入CSSTransition模块
import './style.css'

class App extends Component{
    constructor(props){
        super(props);
        this.state={
          show:true
        };
        this.handleToggle=this.handleToggle.bind(this);
    }

    render() {
        return(
            <Fragment>
                <CSSTransition  // 使用CSSTransition来包含目标标签
                    in={this.state.show}  // 传入的参数,布尔值
                    timeout={1000}  // 动画执行时间
                    classNames="my-node"  // style.css中使用用的类名的前缀
                    unmountOnExit  // 出厂的时候(show=false)是操作目标小时,属性和位置都消失

                    // el就是包含的标签,就是下面的hello,onEnter就是一个钩子函数,出厂的时候执行的操作
                    onEnter={(el)=>{el.style.color='blue'}}

                    // 第一次进入的时候就执行入场效果,在style.css中.my-node-enter 后加上, .my-node-appear 以及.my-node-enter-active后加上 , .my-node-appear-active
                    appear={true}
                >
                    <div>hello</div>
                </CSSTransition>
                {/*<div className={this.state.show ? 'show':'hide'}>hello</div>*/}
                <button onClick={this.handleToggle}>Toggle</button>
            </Fragment>
        )
    }

    handleToggle() {
        this.setState({
            show:this.state.show ? false:true
        })
    }
}


export default App;

// style.css
/*
.my-node:就是CSSTransition中设置的classNames
enter:后缀是enter就说明是入场属性
exit:后缀是exit就说明是出厂属性
appear:后缀是appear就说明是第一次进入的时候要执行的操作
*/

.my-node-enter , .my-node-appear{
    opacity: 0;
}
.my-node-enter-active , .my-node-appear-active{
    opacity: 1;
    /*color: red;*/
    transition: opacity 1s ease-in;
}
.my-node-enter-done{
    opacity: 1;
}


.my-node-exit {
    opacity: 1;
}
.my-node-exit-active {
    opacity: 0;
    transition: opacity 1000ms ease-in;
}

.my-node-exit-done {
    opacity: 0;
}

4-14:使用react-transition-group实现多个元素之间的动画效果

将CSSTransition和transitionGroup都导入,使用transitionGroup内部嵌套CSSTransiton

import React, {Fragment, Component} from 'react'
import {CSSTransition, TransitionGroup} from 'react-transition-group'  // 导入CSSTransition模块
import './style.css'

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            list: []
        };
        this.handleAddItem = this.handleAddItem.bind(this);
    }

    render() {
        return (
            <Fragment>
                {/*这里套一个TransitionGroup*/}
                <TransitionGroup>
                    {
                        // 循环显示list里面的元素
                        this.state.list.map((item, index) => {
                            return (
                                <CSSTransition
                                    timeout={1000}
                                    classNames="my-node"
                                    unmountOnExit
                                    onEnter={(el) => {
                                        el.style.color = 'blue'
                                    }}
                                    appear={true}
                                    // 添加一个index
                                    key={index}
                                >
                                    <div key={index}> {item}</div>
                                </CSSTransition>
                            )
                        })
                    }
                </TransitionGroup>

                {/*点击向list添加元素*/}
                <button onClick={this.handleAddItem}>Toggle</button>
            </Fragment>
        )
    }

    // 向列表中添加一个item
    handleAddItem() {
        this.setState((prevState => {
            return {
                list: [...prevState.list, 'item']
            }
        }))
    }

    handleToggle() {
        this.setState({
            show: this.state.show ? false : true
        })
    }
}


export default App;

第五章:Redux入门