面朝大海,春暖花开
          宫崎骏曾说过一句话:遇见都是天意,拥有的都是幸运,世界上有一千种等待,最好的那种叫“未来可期”

React

基础知识

1.什么是 React ?

一个将数据渲染为 HTML 视图的开源 JavaScript 库。

2.入门案例

<!DOCTYPE html>
<html>
    <head>
        <title>hello react</title>
    </head>

    <body>
        <div id="test"></div>

        <!-- react 核心库,要最先引入-->
        <script type="text/javascript" src="../js/react.development.js"></script>
        <!--用于支持 react 操作dom 的周边库-->
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <!--用于将 jsx 转为 js-->
        <script type="text/javascript" src="../js/babel.min.js"></script>

        <!--代表里面写的内容为 jsx,并将内容翻译为 babel-->
        <script type="text/babel">
            // 创建虚拟 DOM
            const VDOM = <h1>Hello React</h1>;
            // 渲染虚拟 DOM 到页面, render(虚拟DOM,容器)
            ReactDOM.render(VDOM, document.getElementById('test'));
        </script>
    </body>
</html>

3.创建虚拟 DOM 的两种方式 【div > span > h1】

// 采用 jsx 的方式
const DOM1 = <h1 id='title'><span>Hello,React</span></h1>;
// 采用 js 的方式 >> 如果存在多层嵌套关系就会非常繁琐
const DOM2 = React.createElement('h1', {id="title"}, React.createElement('span', {}, 'Hello,React'));

4.虚拟DOM与真实DOM有什么区别?

  • 虚拟 DOM 是一个普通的 Object 对象
  • 虚拟 DOM 的属性比真实 DOM 的属性少
  • 虚拟 DOM 最后会转为真实的 DOM 呈现在页面上

5.jsx 语法规则

  • 定义虚拟 DOM 的时候无能添加引号

  • 标签中混入 JS 表达式时要用 {} 来取值

  • 样式的类名不要用 class,而是使用 className 代替

  • 内联样式要写成 style={{key:value}} 的形式

  • 虚拟 DOM 只能有一个根标签

  • 标签必须闭合

  • 标签的首字母为小写,转化的时候就会去对应 html 中同名标签;如果首字母为大写,那么就会去渲染自定义组件

6.练习 JSX 语法

  • 要求动态展示如下效果:

        <script type="text/babel">
            const data = ['Angular', 'React', 'Vue'];
            const VDOM = (
                <div>
                    <h1>前端JS框架列表</h1>
                    <ul>
                        {
                            data.map((item, index) => {
                                return <li key={index}>{item}</li>;
                            })
                        }
                    </ul>
                </div>
            );

            ReactDOM.render(VDOM, document.querySelector('#test'));
        </script>

对于在 jsx 中想插入 js 表达式可以使用花括号,前提是我们要知道什么是表达式,什么是语句?

  • 例如:a、a + b、demo(1)、arr.map()、function test() 【只要可以被变量接收的都是表达式】
  • 对于 if、for、swith 等都是语句,也就是说不能写在 jsx 的 {} 中

7.如何创建一个函数式组件?

<script type="test/babel">
    // 1.创建函数式组件
	function MyComponent(){
        return <h1>我是函数定义的组件,适用于简单组件</h1>
    }
    
    // 2.渲染组件到页面中
    ReactDOM.render(<MyComponent />, document.getElementById('test'));
</script>

这部分的工作内容有两个:

  • React 解析组件标签,找到了 MyComponent 组件
  • 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。

8.复习一下类的使用:

  • 使用 constructor 关键字声明类的构造器,没有需求可以不创建构造器,当需要添加特定的属性就需要创建构造器
  • 如果两个类存在继承关系,子类想在构造器中添加字段,首行必须调用 super 使用父类的构造器
  • 类中所定义的方法,都是放在了类的原型对象上,供实例去使用

9.如何创建一个类组件?

<script type="text/javascript" src="../js/babel.min.js"></script>
        
<script type="text/babel">
    // 1.定义类式组件
    class MyComponent extends React.Component{
        render(){
                return <h2>我是一个类式组件,可以用于实现一些复杂的组件</h2>;
        }
    }
	// 2.将组件渲染到页面中
	ReactDOM.render(<MyComponent />, document.getElementById('test'));
</script>

对它的流程也描述一下:

  • React 解析组件标签,找到了 MyComponent 组件
  • 发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法
  • 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中。

10.如何借助构造器初始化状态,并读取出状态?

<script type="text/babel">
    // 1.创建组件
    class Weather extends React.Component{
        constructor(props){
            super(props)
            this.state = {isHot : false}
        }

        render(){
            const {isHot} = this.state
            return <h1>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
        }
    }
    // 2.渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById('test'));
</script>

11.如何在 React 中如何绑定点击事件?

<script type="text/babel">
    function demo(){
    	alert('点击绑定事件');
    }

    class Weather extends React.Component{
        constructor(props){
            super(props)
            this.state = {isHot : false}
        }
        render(){
            const {isHot} = this.state
            // onclik 在 React 中为 onClick
            return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
        }
    }

	ReactDOM.render(<Weather />, document.getElementById('test'));
</script>

12.类中函数回调的时候,不是类的实例调的,属于直接调的该方法,this 为 undefine


1.回顾上集,那么应该如何通过 state 来实现点击切换呢?

<!DOCTYPE html>
<html>
    <head>
        <title>底板</title>
    </head>

    <body>
        <div id="test"></div>
        <script type="text/javascript" src="../js/react.development.js"></script>
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <script type="text/javascript" src="../js/babel.min.js"></script>
        
        <script type="text/babel">
            function demo(){
                alert('点击绑定事件');
            }

            class Weather extends React.Component{
                constructor(props){
                    super(props)
                    this.state = {isHot : false}
                    // 作用就是将这个方法绑定到 Wether 的实例上, bind 方法会返回一个新函数
                    this.changeWeather = this.changeWeather.bind(this)
                }

                render(){
                    const {isHot} = this.state
                    // onclik 在 React 中为 onClick
                    return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
                }
                changeWeather(){
                    const isHot = this.state.isHot;
                    // 状态不能直接修改
                    // this.state.isHot = !isHot;
                    this.setState({isHot:!isHot});
                }
            }

            ReactDOM.render(<Weather />, document.getElementById('test'))
        </script>
    </body>
</html>

下面是代码的简化版本:

<!DOCTYPE html>
<html>
    <head>
        <title>底板</title>
    </head>

    <body>
        <div id="test"></div>
        <script type="text/javascript" src="../js/react.development.js"></script>
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <script type="text/javascript" src="../js/babel.min.js"></script>
        
        <script type="text/babel">

            class Weather extends React.Component{

                state = {isHot : false};

                changeWeather = ()=>{
                    const isHot = this.state.isHot;
                    this.setState({isHot:!isHot});
                }

                render(){
                    const {isHot} = this.state
                    return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '寒冷'}</h1>
                }
            }

            ReactDOM.render(<Weather />, document.getElementById('test'))
        </script>
    </body>
</html>

2.组件第二大核心属性 props,演示一下如何通过 props 外部传递参数值?

<script type="text/babel">
            // 创建组件
            class Person extends React.Component {
                render(){
                    const {name, sex, age} = this.props;
                    return (
                        <ul>
                            <li>姓名:{name}</li>
                            <li>性别:{sex}</li>
                            <li>年龄:{age}</li>
                        </ul>
                    )
                }
            }

            // 渲染组件
            ReactDOM.render(<Person name="张三" sex="男" age="21"/>, document.querySelector('#test'));
</script>

此处传递参数可以用展开运算符

const args = {name="张三" sex="男" age="21"};
ReactDOM.render(<Person {...args}/>, document.querySelector('#test'));

展开运算符正常是不可以展开对象的,一般用于数组中比较多(复制、连接),因为 babel + react 实现了展开对象

// 复制数组
const arr1 = [1, 2, 3];
// const arr2 = arr1;  属于引用传递
const arr2 = {...arr1};
// 连接数组
const arr3 = {...arr1, ....arr2};

// 在函数中使用
function sun(...numbers){
    return numbers.reduce((preNumber, curNumber) =>{
        return preNumber + curNumber;
    })
}

// 构架字面量
let person = {name:'tom', age : 18};
let person2 = {...person}
let person3 = {...person, name:"jack", address:"中国"}

3.对 props 中数据类型限制,参数必要性限制

// 在 React 16.X 版本,开始将 props 属性限制的依赖分出一个单独的依赖包
<script type="text/javascript" src="../js/prop-types.js"></script>
// 定义规则和默认值,下面这两段写在 script 标签里
Person.propTypes = {
	name: PropTypes.string.isRequired,
	sex: PropTypes.string,
	age: PropTypes.number,
	speak: propTypes.func,
}
Person.defaultProp = {
	sex: "不男不女",
	age: 18,
}
// 简化版本
<!DOCTYPE html>
<html>
    <head>
        <title>props 的使用</title>
    </head>

    <body>
        <div id="test"></div>
        <script type="text/javascript" src="../js/react.development.js"></script>
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <script type="text/javascript" src="../js/babel.min.js"></script>
        <script type="text/javascript" src="../js/prop-types.js"></script>
        
        <script type="text/babel">
            // 创建组件
            class Person extends React.Component {
                static propTypes = {
                    name: PropTypes.string.isRequired,
                    sex: PropTypes.string,
                    age: PropTypes.number,
                    speak: propTypes.func,
                }
                static defaultProps = {
                    sex: "不男不女",
                    age: 18,
                }
                render(){
                    const {name, sex, age} = this.props;
                    return (
                        <ul>
                            <li>姓名:{name}</li>
                            <li>性别:{sex}</li>
                            <li>年龄:{age}</li>
                        </ul>
                    )
                }
            }

            // 渲染组件
            ReactDOM.render(<Person name="张三" sex="男" age="21"/>, document.querySelector('#test'));
        </script>
    </body>
</html>

4.类组件和函数式组件对于 props 的使用:

(1)类组件的构造器中可以使用 props,但一定要调用 super 传递 props

class Person extends React.Component {
    constructor(props){
        super(props);
    }
    static propTypes = {
        
    }
	static defaultProps = {
        
    }
}

(2)函数式组件可以使用 props,但是不能使用 state 和 refs

function Person(props){
    const {name, age, sex} = props;
    return (
    	<ul>
        	<li>{姓名: name}</li>
            <li>{性别: sex}</li>
            <li>{年龄: age}</li>
        </ul>
    );
}
Person.propTypes = {
    
}
Person.defaultProps = {
    
}

5.ref 的三种形式:

(1)字符串形式的 ref >> 已经不推荐使用,因为效率比较低

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <div id="test"></div>


    <script type="text/babel">
        class Demo extends React.Component {
            showData = () => {
                const { input1 } = this.refs
                alert(input1.value)
            }
            // Shift + Alt + F 快速格式化代码
            loseBlur = () => {
                const { input2 } = this.refs
                alert(input2.value)
            }
            render() {
                return (
                    <div>
                        <input ref="input1" type="text" placeholder="点击按钮提示数据" />
                        <button ref="button1" onClick={this.showData}>点击提示左侧数据</button> &nbsp;
                        <input ref="input2" onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
                    </div>
                );
            }
        }
        ReactDOM.render(<Demo />, document.getElementById('test'));
    </script>
</body>

</html>

(2)回调函数的 ref >> 在更新的时候可能会出现调用两次的现象

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <div id="test"></div>


    <script type="text/babel">
        class Demo extends React.Component {
            showData = () => {
                const { input1 } = this
                alert(input1.value)
            }
            // Shift + Alt + F 快速格式化代码
            loseBlur = () => {
                const { input2 } = this
                alert(input2.value)
            }
            render() {
                return (
                    <div>
                        <input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧数据</button> &nbsp;
                        <input ref={currentNode => this.input2 = currentNode} onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
                    </div>
                );
            }
        }
        ReactDOM.render(<Demo />, document.getElementById('test'));
    </script>
</body>

</html>

可以通过将匿名函数赋值给类实例本身,然后再赋值给 ref

// 写在类组件中
saveInput = (c) => {
	this.input1 = c;
}
// 写在 render 方法的 return 中的具体的一个标签中
<input ref=this.saveInput type="text" /> >

(3)通过 React 的 createRef API,但是注意是专人专用的,一次 createRef 创建的容器只能存储一个节点

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- Shift + Alt + 向下箭头,快速复制当前行到下一行 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <div id="test"></div>


    <script type="text/babel">
        class Demo extends React.Component {
            // 在类组件中创建容器
            myRef = React.createRef()
            myRef2 = React.createRef()

            showData = () => {
                alert(this.myRef.current.value)
            }
            // Shift + Alt + F 快速格式化代码
            loseBlur = () => {
                alert(this.myRef2.current.value)
            }
            render() {
                return (
                    <div>
                        <input ref= {this.myRef} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧数据</button> &nbsp;
                        <input ref= {this.myRef2} onBlur={this.loseBlur} type="text" placeholder="失去焦点提示数据" />
                    </div>
                );
            }
        }
        ReactDOM.render(<Demo />, document.getElementById('test'));
    </script>
</body>

</html>

1.react 收集表单数据

  • 非受控组件就是数据现用现取
  • 非受控组件就是提前将数据存好

(1)非受控组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <script type="text/babel">
        // 1.创建组件
        class Login extends React.Component {
            getData = (event) => {
                event.preventDefault()
                const {username, password} = this
                alert(`账号为 ${username.value}, 密码为${password.value}`)
            }
            render(){
                return (
                    <form onSubmit={this.getData}>
                        账号:<input ref = {u => this.username = u} type="text" name="username"/>    
                        密码:<input ref = {p => this.password = p} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件
        ReactDOM.render(<Login/>, document.getElementById('test'));
    </script>
</body>
</html>

(2)受控组件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <script type="text/babel">
        // 1.创建组件
        class Login extends React.Component {
            // 初始状态
            state = {
                username : '',
                password : ''
            }
            // 回调函数
            getData = (event) => {
                const {username, password} = this.state
                alert(`账号为 ${username}, 密码为${password}`)
                event.preventDefault()
            }
            // 将表单中用户名存储到 state 中
            saveUsername = (event) => {
                this.setState({ username: event.target.value })
            }
            // 将表单中密码存储到 state 中
            savePassword = (event) => {
                this.setState({ password: event.target.value })
            }
            // 返回 React 元素
            render() {
                return (
                    <form onSubmit={this.getData}>
                        账号:<input type="text" name="username" onChange={this.saveUsername}/>
                        密码:<input type="password" name="password" onChange={this.savePassword}/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件
        ReactDOM.render(<Login />, document.getElementById('test'));
    </script>
</body>

</html>

上面的代码存在一个问题,如果表单数据很多,就会出现大量重复的 saveXXX,那么如何解决呢?

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <script type="text/babel">
        // 1.创建组件
        class Login extends React.Component {
            // 初始状态
            state = {
                username : '',
                password : ''
            }
            // 回调函数
            getData = (event) => {
                const {username, password} = this.state
                alert(`账号为 ${username}, 密码为${password}`)
                event.preventDefault()
            }
            // 保存数据到 state 中
            saveFormData = (dataType) => {
                return (event) => {
                    this.setState({[dataType] : event.target.value})
                }
            }
            
            // 返回 React 元素
            render() {
                return (
                    <form onSubmit={this.getData}>
                        账号:<input type="text" name="username" onChange={this.saveFormData('username')}/>
                        密码:<input type="password" name="password" onChange={this.saveFormData('password')}/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件
        ReactDOM.render(<Login />, document.getElementById('test'));
    </script>
</body>

</html>

如果不用函数柯里化也是可以实现的

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/javascript" src="../js/prop-types.js"></script>

    <script type="text/babel">
        // 1.创建组件
        class Login extends React.Component {
            // 初始状态
            state = {
                username : '',
                password : ''
            }
            // 回调函数
            getData = (event) => {
                const {username, password} = this.state
                alert(`账号为 ${username}, 密码为${password}`)
                event.preventDefault()
            }
            // 保存数据到 state 中
            saveFormData = (dataType, value) => {       
                this.setState({[dataType] : value})           
            }
            
            // 返回 React 元素
            render() {
                return (
                    <form onSubmit={this.getData}>
                        账号:<input type="text" name="username" onChange={(e) => {this.saveFormData('username', e.target.value)}}/>
                        密码:<input type="password" name="password" onChange={(e) => {this.saveFormData('password', e.target.value)}}/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        // 2.渲染组件
        ReactDOM.render(<Login />, document.getElementById('test'));
    </script>
</body>

</html>

2.引出 React 的声明周期

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>1_引出生命周期</title>
</head>
<body>
	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/react.development.js"></script>
	<!-- 引入react-dom,用于支持react操作DOM -->
	<script type="text/javascript" src="../js/react-dom.development.js"></script>
	<!-- 引入babel,用于将jsx转为js -->
	<script type="text/javascript" src="../js/babel.min.js"></script>

	<script type="text/babel">
		//创建组件
		//生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
		class Life extends React.Component{

			state = {opacity:1}

			death = ()=>{
				//卸载组件
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			//组件挂完毕
			componentDidMount(){
				console.log('componentDidMount');
				this.timer = setInterval(() => {
					//获取原状态
					let {opacity} = this.state
					//减小0.1
					opacity -= 0.1
					if(opacity <= 0) opacity = 1
					//设置新的透明度
					this.setState({opacity})
				}, 200);
			}

			//组件将要卸载
			componentWillUnmount(){
				//清除定时器
				clearInterval(this.timer)
			}

			//初始化渲染、状态更新之后
			render(){
				console.log('render');
				return(
					<div>
						<h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
						<button onClick={this.death}>不活了</button>
					</div>
				)
			}
		}
		//渲染组件
		ReactDOM.render(<Life/>,document.getElementById('test'))
	</script>
</body>
</html>

3.React 生命周期


脚手架

1.使用脚手架快速生成一个最基础的模版?

// 1.全局安装手脚架库 create-react-app
npm i create-react-app -g
// 2.切换到待创建项目的目录,创建项目
create-react-app hello-react
// 3.进入到生成的项目文件夹
cd hello-react
// 4.启动项目
npm start

2.基于生成的模版写一个最简单的 Hello 应用

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <title>React 脚手架</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

Hello.js

import React, {Component} from 'react'
import './Hello.css'

export default class Hello extends Component {
    render(){
        return <h1 className='title'>Hello, React</h1>
    }
}

Hello.css

.title {
    background-color: aqua;
}

Welcome.js

import React, {Component} from 'react'
import './Welcome.css'
export default class Welcome extends Component {
    render(){
        return <h2 className='demo'>Welcome</h2>
    }
}

Welcome.css

.demo {
    background-color: chartreuse;
}

App.js

// 创建外壳组件
// 引入 React 核心库,因为 Component 和 React 采用了分别暴露才可以这样写,并不是解构赋值
import React, {Component} from 'react'
// 引入 Hello 组件
import Hello from './components/Hello/Hello.js'
import Welcome from './components/Welcome/Welcome.js'

// 创建并暴露 App 组件
export default class App extends Component {
    render() {
        return (
            <div>
                <Hello />
                <Welcome />
            </div>

        );
    }
}

// 采用默认暴露暴露组件
// export default App

index.js

// 引入 React 核心库
import React from 'react'
// 引入操作 dom 的组件
import ReactDOM from 'react-dom'
// 引入我们要渲染的组件
import App from './App'

// 渲染组件到 root 中
ReactDOM.render(<App/>, document.getElementById('root'));

3.如果多个组件里存在同名的样式,可能会出现冲突,所以介绍一下样式的模块化

在样式命名的时候中间加上 module,例如: index.module.css

在我们组件中引入样式就可以采用 import XXX from './index.module.css' 【原来 import './index.css'】

在使用 className 的时候,就要用 {} 取值,例如: className =

4.ToDo 案例

功能点介绍:

  • 可以通过输入框添加任务到任务列表
  • 当鼠标移动到具体的某个任务上任务条背景色变灰色,显示删除任务按钮,点击删除按钮会进行二次确
  • 当所有任务被勾选,总复选框会被勾选,点击不勾选总复选框会清空所有任务复选框的勾选,根据勾选情况统计已完成和全部的任务
  • 点击清除已完成的任务会删除所有勾选的任务

项目模块图如下:

代码内容如下:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <title>React 脚手架</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

Footer - index.css

  /*footer*/
  .todo-footer {
      height: 40px;
      line-height: 40px;
      padding-left: 6px;
      margin-top: 5px;
  }

  .todo-footer label {
      display: inline-block;
      margin-right: 20px;
      cursor: pointer;
  }

  .todo-footer label input {
      position: relative;
      top: -1px;
      vertical-align: middle;
      margin-right: 5px;
  }

  .todo-footer button {
      float: right;
      margin-top: 5px;
  }

Footer - index.jsx

import React, { Component } from 'react'
import './index.css'

export default class index extends Component {
    // 全选复选框
    handleCheckAll = (event) => {
        this.props.checkAllTodo(event.target.checked)
    }

    // 清除所有已完成的 todo
    handleClearAllDone = () => {
        this.props.clearAllTodo()
    }
    
    render() {
        const {todos} = this.props
        const doneCount = todos.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
        const total = todos.length
        return (
            <div className="todo-footer">
                <label>
                    {/* 此处之所以不用 defaultChecked,是因为 defaultChecked 只有在第一次的时候生效,我们 checked 是以最后一次的为准 */}
                    <input type="checkbox" checked={doneCount === total && total !== 0 ? true : false} onChange={this.handleCheckAll}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
            </div>
        )
    }
}

Header- index.css

  /*header*/
  .todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }
  
  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }

Header- index.jsx

import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import PropTypes from 'prop-types'

import './index.css'

export default class index extends Component {
    // 参数类型与必要性限制
    static propTypes = {
        addTodo: PropTypes.func.isRequired
    }
	// 输入框按键回调函数
    handleKeyUp = (e) => {
        const {keyCode, target} = e
        if(keyCode !== 13) return
        if(target.value.trim() === ''){
            alert('输入不能为空')
            return
        }
        const todoObj = {id:nanoid(), name: target.value, done: false}
        this.props.addTodo(todoObj)
        target.value = ''
    }
    
    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp}  type="text" placeholder="请输入你的任务名称,按回车键确认" />
            </div>
        )
    }
}

Item- index.css

/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}

Item- index.jsx

import React, { Component } from 'react'
import './index.css'

export default class index extends Component {
    // 鼠标移入、移除
    state = {mouse: false}
	// 鼠标回调函数
    handleMoouse = (flag) => {
        return () => {
            this.setState({mouse : flag})
        }
    }
	// 任务复选框回调函数
    hanleCheck = (id) => {
        return (event) => {
            this.props.updateTodo(id, event.target.checked)            
        }
    }
	// 删除按钮回调函数
    handleDelete = (id) => {
        if(window.confirm('确定删除吗?')){
            this.props.deleteTodo(id)
        }
    }

    render() {
        const { id, name, done } = this.props
        const {mouse} = this.state
        return (
            <li style={{backgroundColor: mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMoouse(true)} onMouseLeave={this.handleMoouse(false)}>
                <label>
                    <input type="checkbox" checked={done} onChange = {this.hanleCheck(id)}/>
                    <span>{name}</span>
                </label>
                <button onClick={() => this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse ? 'block' : 'none'}}>删除</button>
            </li>
        )
    }
}

List- index.css

  /*main*/
  .todo-main {
      margin-left: 0px;
      border: 1px solid #ddd;
      border-radius: 2px;
      padding: 0px;
  }

  .todo-empty {
      height: 40px;
      line-height: 40px;
      border: 1px solid #ddd;
      border-radius: 2px;
      padding-left: 5px;
      margin-top: 10px;
  }

List- index.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item'

import './index.css'

export default class index extends Component {
    // 参数类型与必要性限制
    static propTypes = {
        todos: PropTypes.array.isRequired,
        updateTodo: PropTypes.func.isRequired,
        deleteTodo: PropTypes.func.isRequired
    }
    render() {
        const { todos, updateTodo, deleteTodo } = this.props
        return (
            <ul className="todo-main">
                {
                    todos.map((todo) => {
                        return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
                    })
                }
            </ul>
        )
    }
}

App.css

/*base*/
body {
    background: #fff;
  }
  
  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
  
  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }
  
  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }
  
  .btn:focus {
    outline: none;
  }
  
  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }

App.jsx

import React, { Component } from 'react'
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'

import './App.css'

export default class App extends Component {
    // 我们将要维护的数据写在父组件的状态中,这样方便将数据传递给子组件和子组件将数据赋值给父组件 [状态在哪操作状态的方法就在哪]
    state = {
        todos: [
            { id: '001', name: '吃饭', done: true },
            { id: '002', name: '睡觉', done: true },
            { id: '003', name: '骑车', done: false },
            { id: '004', name: '写代码', done: true }
        ]
    }
    // 添加 todo
    addTodo = (todoObj) => {
        // 获取原数组
        const { todos } = this.state
        // 创建新数组
        const newTodos = [todoObj, ...todos]
        // 更新状态
        this.setState({ todos: newTodos })
    }
    // 更新 todo
    updateTodo = (id, done) => {
        const { todos } = this.state
        const newTodos = todos.map((todoObj) => {
            if (todoObj.id === id) {
                // 复制对象并修改指定的属性为新属性值
                return { ...todoObj, done: done }
            } else {
                return todoObj
            }
        })
        this.setState({ todos: newTodos })

    }
    // 删除 todo
    deleteTodo = (id) => {
        const { todos } = this.state
        const newTodos = todos.filter((todoObj) => {
            return todoObj.id !== id
        })
        this.setState({ todos: newTodos })
    }

    // 更新所有 todo 状态
    checkAllTodo = (check) => {
        const { todos } = this.state
        const newTodos = todos.map((todoObj) => {
            return { ...todoObj, done: check }
        })
        this.setState({ todos: newTodos })
    }

    // 清除所有已完成的 todo
    clearAllTodo = () => {
        const { todos } = this.state
        const newTodos = todos.filter((todoObj) => {
            return !todoObj.done
        })
        this.setState({ todos: newTodos })
    }

    render() {
        const { todos } = this.state
        return (
            <div className="todo-container">
                <div className="todo-wrap">
                    <Header addTodo={this.addTodo} />
                    <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo} />
                    <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllTodo={this.clearAllTodo}/>
                </div>
            </div>
        )
    }
}

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

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

React Ajax

1.react脚手架配置代理总结
(1) 方法一

在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  • 优点:配置简单,前端请求资源时可以不加任何前缀。
  • 缺点:不能配置多个代理。
  • 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

(2) 方法二

第一步:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

编写setupProxy.js配置具体代理规则:

const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
      	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}

说明:

  • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  • 缺点:配置繁琐,前端请求资源时必须加前缀。

2.React 代理配置案例
App.jsx

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

export default class App extends Component {
    getStudentData = () => {
        axios.get('http://localhost:3000/api1/students').then(
            response => {console.log('成功了', response.data)},
            error => {console.log('失败了', error)}
        )
    }
    getCarData = () => {
        axios.get('http://localhost:3000/api2/cars').then(
            response => {console.log('成功了', response.data)},
            error => {console.log('失败了', error)}
        )
    }

    render() {
        return (
            <div>
                <button onClick={this.getStudentData}>点击获取学生数据</button>
                <button onClick={this.getCarData}>点击获取汽车数据</button>
            </div>
        )
    }
}

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'

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

setupProxy.js

// const proxy = require('http-proxy-middleware')

// module.exports = function(app){
// 	app.use(
// 		proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
// 			target:'http://localhost:5000', //请求转发给谁
// 			changeOrigin:true,//控制服务器收到的请求头中Host的值
// 			pathRewrite:{'^/api1':''} //重写请求路径(必须)
// 		}),
// 		proxy('/api2',{
// 			target:'http://localhost:5001',
// 			changeOrigin:true,
// 			pathRewrite:{'^/api2':''}
// 		}),
// 	)
// }
// 高版本
const proxy = require('http-proxy-middleware')//引入http-proxy-middleware,react脚手架已经安装

module.exports = function(app){
	app.use(
		proxy.createProxyMiddleware('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
			target:'http://localhost:5000', //请求转发给谁
			changeOrigin:true,//控制服务器收到的请求头中Host的值
			pathRewrite:{'^/api1':''} //重写请求路径,下面有示例解释
		}),
		proxy.createProxyMiddleware('/api2',{
			target:'http://localhost:5001',
			changeOrigin:true,
			pathRewrite:{'^/api2':''}
		}),
	)
}

3.GitHub 搜索案例-使用父组件的状态传递数据-axios

项目目录:

代码:
List-index.css

.album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
  }
  
  .card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
  }
  
  .card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
  }
  
  .card-text {
    font-size: 85%;
  }

List-index.jsx

import React, { Component } from 'react'
import './index.css'
export default class List extends Component {
    render() {
        const {isFirst, isLoading, users, err} = this.props
        return (
            <div className="row">
                {
                    isFirst ? <h2>点击搜索按钮查询用户数据</h2> :
                    isLoading ? <h2>Loading ...</h2> :
                    err ? <h2 style={{color:'red'}}>{err}</h2> :
                    users.map((user) => {
                        return (
                            <div key={user.id} className="card">
                                <a rel="noreferrer" href={user.html_url} target="_blank">
                                    <img alt="can't load" src={user.avatar_url} style={{ width: '100px' }} />
                                </a>
                                <p className="card-text">{user.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )



    }
}

Search-index.jsx

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

export default class Search extends Component {
    search = () => {
        // 获取到输入框中输入的内容
        const { keyWordElement: { value: keyWord } } = this
        const { updateAppState } = this.props
        // 发送请求前更新状态
        updateAppState({ isFirst: false, isLoading: true })
        // 发送请求
        axios.get(`http://localhost:3000/api/search/users?q=${keyWord}`).then(
            // 发送请求后更新状态     
            response => {
                updateAppState({ isLoading: false, users: response.data.items })
            },
            error => {
                updateAppState({ isLoading: false, err: error })
            }
        )
    }

    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">Search Github Users</h3>
                <div>
                    <input ref={(c) => { this.keyWordElement = c }} type="text" placeholder="enter the name you search" />&nbsp;<button onClick={this.search}>Search</button>
                </div>
            </section>
        )
    }
}

App.jsx

import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
  state = {
    users:[],
    isFirst: true,
    isLoading: false,
    err: ''
  }

  updateAppState = (stateObj) => {
    this.setState(stateObj)
  }

  render() {
    return (
        <div id="app">
        <div className="container">
            <Search updateAppState={this.updateAppState}/>
            <List {...this.state}/>
        </div>
      </div>
    )
  }
}

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx'

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

setupProxy.js

const proxy = require('http-proxy-middleware') 

module.exports = function(app){
    app.use(
        proxy.createProxyMiddleware('/api', {
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite:{'^/api' : ''}
        })
    )
}

4.GitHub 搜索案例-发布订阅传递兄弟组件(适用于所有组件之间)数据-pubsub
与上面的案例只有以下几个文件存在差异:
Search-index.jsx

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'

export default class Search extends Component {
    search = () => {
        // 获取到输入框中输入的内容
        const { keyWordElement: { value: keyWord } } = this

        // 发送请求前更新状态
        PubSub.publish('test message', { isFirst: false, isLoading: true })
        // 发送请求
        axios.get(`http://localhost:3000/api/search/users?q=${keyWord}`).then(
            // 发送请求后更新状态     
            response => {
                PubSub.publish('test message', { isLoading: false, users: response.data.items })
            },
            error => {
                PubSub.publish('test message', { isLoading: false, err: error })
            }
        )
    }

    render() {
        return (
            <section className="jumbotron">
                <h3 className="jumbotron-heading">Search Github Users</h3>
                <div>
                    <input ref={(c) => { this.keyWordElement = c }} type="text" placeholder="enter the name you search" />&nbsp;<button onClick={this.search}>Search</button>
                </div>
            </section>
        )
    }
}

List-index.jsx

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
export default class List extends Component {
    state = {
        users:[],
        isFirst: true,
        isLoading: false,
        err: ''
      }
    componentDidMount(){
        this.token = PubSub.subscribe("test message", (_, stateObj) => {
            this.setState(stateObj)
        })
    }

    componentWillUnmount(){
        PubSub.unsubscribe(this.token)
    }

    render() {
        const {isFirst, isLoading, users, err} = this.state
        return (
            <div className="row">
                {
                    isFirst ? <h2>点击搜索按钮查询用户数据</h2> :
                    isLoading ? <h2>Loading ...</h2> :
                    err ? <h2 style={{color:'red'}}>{err}</h2> :
                    users.map((user) => {
                        return (
                            <div key={user.id} className="card">
                                <a rel="noreferrer" href={user.html_url} target="_blank">
                                    <img alt="can't load" src={user.avatar_url} style={{ width: '100px' }} />
                                </a>
                                <p className="card-text">{user.login}</p>
                            </div>
                        )
                    })
                }
            </div>
        )



    }
}

App.jsx

import React, { Component } from 'react'
import Search from './components/Search'
import List from './components/List'
export default class App extends Component {
  render() {
    return (
        <div id="app">
        <div className="container">
            <Search />
            <List />
        </div>
      </div>
    )
  }
}

1.使用 fetch 发送 ajax 请求,它的级别和 xhr 是相同的,不需要引入额外的依赖

(1)最基础的版本

fetch(url).then(
	response => {
        console.log('联系服务器成功了');
        // 此处返回的是一个 Promise 对象
        return response.json()
    },
    error => {
        console.log('联系服务器失败了', error)
        return new Promise(() => {})
    }
).then(
	response => {console.log('获取数据成功了', response)},
    error => {console.log('获取数据失败了', error)}
)

(2)改进版本,将 error 整合到一起

fetch(url).then(
	response => {
        console.log('联系服务器成功了');
        // 此处返回的是一个 Promise 对象
        return response.json()
    }
).then(
	response => {console.log('获取数据成功了', response)},
).catch(
	(error) => {console.log('发送请求失败', error)}
)

(3)改进版本,不用多个 then,使用 await 【await关键字是异步编程中用于等待 Promise 对象解决的一种机制】

try{
    const response = await fetch(url)
    const data = await response.json()
    console.log('获取数据成功了', data)
}catch(error){
    console.log('发送请求失败', error)
}
posted @ 2023-10-06 12:04  小赵同学爱编码  阅读(22)  评论(0编辑  收藏  举报