React小项目实战、代码解读及组件化开发

前言

在这个快节奏的时代,前端技术的发展日新月异,小项目实践成为了我们掌握新技能、深化理解的重要途径。本文将从一个简单而有趣的小项目出发,带你逐步探索React(或类似前端框架)的精髓。从form表单的基本使用到列表渲染的实现,再到项目代码的深度解读,每一步都旨在帮助你构建坚实的知识基础。
特别地,我们将关注于项目的交互逻辑与用户体验,通过实现“选择学习语言”这一功能,你将学习到如何在React中处理用户输入、管理状态变化、以及实现条件渲染等核心技能。同时,你还将了解到在组件化开发中如何组织代码、提高复用性,并掌握组件间数据传递的技巧。
此外,本文还将深入探讨React中常见的问题之一——this的丢失,并介绍如何通过绑定事件处理器来有效解决这一问题。通过这一系列的学习与实践,你将能够更加自信地应对前端开发中的挑战,为自己的技术栈增添新的亮点。
让我们一起踏上这段精彩的旅程,通过小项目的实践,共同探索React的无限可能!

1、小项目介绍与展示

在这里插入图片描述

1-1、项目初始的时候下面的语言是空的,语言列表是空的时候,那么选择学习语言的按钮就是不能点击的状态;
1-2、当添加语言为空时,会提示你输入语言;
1-3、当语言列表不为空的时候点击选择学习语言时会随机选择列表中的一种语言;
1-4、点击清除按钮会把语言全部清除;

完成小项目之前先来试试react中input框的使用吧。

2、form表单input框初使用

我们把src文件夹下的app.js改成如下代码(运行的命令就不在这本赘述了,可以看这里 1、React环境搭建,以及初识jsx语法。):

let inputFunc = (e) => {
    e.preventDefault()
    console.log(e.target.elements.input.value);
    reactRenderFunc()
}

const reactRenderFunc = () => {
    const template = (
        <div>
            <form  onSubmit={inputFunc}>
                <input type="text" name="input"></input>
                <button>
                    点击
                </button>
            </form>
        </div>
    )
    ReactDOM.render(template, document.getElementById('app'))
}

reactRenderFunc()

这段代码中,表单上绑定了原生的submit事件,并用inputFunc函数接收这个事件,在inputFunc函数中我们取消了表单的默认行为e.preventDefault(),(表单的默认行为会想action属性发起请求,默认get方法,当然我们这边没有写action属性,所以我们取消它的默认行为)当我们点击button按钮时就会触发这个submit事件,我们在inputFunc函数中打印出了每次输入的值。如下:
在这里插入图片描述
表单中的input框暂时了解到这,在我们的小项目中已经够用,后续再深入了解。

3、列表渲染

我们知道在实际的开发中,前端发送ajax请求获取后端数据,进行渲染,那么渲染列表是很经常的事,那么我们来简单了解下react中对列表渲染。修改src文件夹下的app.js为:

let lanArr = [
    'react',
    'vue',
    'node',
    'php'
]

const reactRenderFunc = () => {
    const template = (
        <div>
            {
                lanArr.map((item) => {
                    return  <div>
                                {item}: <input type="text"></input>
                            </div>    
                })
            }
        </div>
    )
    ReactDOM.render(template, document.getElementById('app'))
}

reactRenderFunc()

我们采用js的map方法对数组进行循环,可以看到页面已经渲染了4个input框:
在这里插入图片描述
此时我们打开控制台发现已经报错了,具体报错如下:
在这里插入图片描述
什么意思呢,意思就是每个循环的都必须要有个key值,这个key值什么用?我们写个例子,将src下的app.js写成如下:

let lanArr = [
    'react',
    'vue',
    'node',
    'php'
]
let reverseFunc = () => {
    lanArr.reverse()
    reactRenderFunc()
}
const reactRenderFunc = () => {
    const template = (
        <div>
            {
                lanArr.map((item) => {
                    return  <div>
                                {item}: <input type="text"></input>
                            </div>    
                })
            }
            <button onClick={reverseFunc}>倒置</button>
        </div>
    )
    ReactDOM.render(template, document.getElementById('app'))
}

reactRenderFunc()

我们添加了一个倒置的功能,当点击倒置按钮时,数组会倒置,并且数据重新渲染,功能如下:
在这里插入图片描述
我们可以发现点击后数据会进行倒置,那么我们在每个input框里去写值,然后再倒置看看,如下:
在这里插入图片描述
我们发现input框根本没有改变顺序,那么这就是key的关键了,react中在列表渲染中需要绑定key值来唯一标识每个渲染的值,代码中将key值加入:

lanArr.map((item) => {
                    return  <div key={item}>
                                {item}: <input type="text"></input>
                            </div>    
                })

效果如下:
在这里插入图片描述
可以看到如果有了key值,它就能知道我们这里的input框是不一样的了,这样的话就会进行对比,就会改变。

4、小项目代码解读

将src文件夹下的app.js写成如下:

let obj = {
    "title": "今天学习什么语言?",
    "des": "大家好,我是Counterrr",
    "tips": "不忘初心,砥砺前行",
    "languages": []
}
// 清除所有语言
let removeAllFunc = () => {
    obj.languages = []
    reactRenderFunc();
}
let selectLanFunc = () => {
    let len = obj.languages.length;
    let randomNum = Math.floor(Math.random() * len);
    alert(`今天学习的语言是: ${obj.languages[randomNum]}`);
}
let submitFunc = (e) => {
    e.preventDefault();
    if (e.target.elements.languages.value == '') {
        alert('请输入!');
    }
    else {
        obj.languages.push(e.target.elements.languages.value);
        e.target.elements.languages.value = '';
        reactRenderFunc();
    }
}
const reactRenderFunc = () => {
    const template = (
        <div>
            <header>
                <div>{obj.title}</div>
                <div>{obj.des}</div>
                <div>{obj.tips}</div>
            </header>
            <main>
                <button onClick={removeAllFunc}>清除</button>
                <button disabled={obj.languages.length == 0} onClick={selectLanFunc}>选择学习的语言</button>
                
                <form onSubmit={submitFunc}>
                    <div>
                        <input type="text" name="languages"></input>
                        <button>添加语言</button>
                    </div>
                </form>
                <ul>
                    {
                        obj.languages.map((item, index) => {
                        return <li key={index}>{item}</li>
                        })
                    }
                </ul>
            </main>
        </div>
    )
    ReactDOM.render(template, document.getElementById('app'))
}

reactRenderFunc()

4.1 项目初始的时候下面的语言是空的,语言列表是空的时候,那么选择学习语言的按钮就是不能点击的状态

刚开始语言列表为空,我们可以将obj对象下的languages数组进行map循环,渲染li标签,有多少项就全部渲染出来,如果是空的话就为空,我们在学习语言的按钮上绑定disabled原生属性,并且在花括号里去写obj.languages.length == 0 判断语言数组是否为空,为空的话这个表达式就为true,那么这个按钮就不能点击,反之有长度则为false,按钮就可以点击。

4.2 当添加语言为空时,会提示你输入语言

点击添加语言出发submit事件,触发submitFunc函数,去判断input框里的值是否为空,如果为空的话,则用系统自带的弹窗alert( )进行弹窗提示请输入。

4.3 当语言列表不为空的时候点击选择学习语言时会随机选择列表中的一种语言

点击选择学习语言的按钮时,会触发selectLanFunc函数,selectLanFunc函数中先获取了对象objlanguages数组的长度,并用Math.random()函数,随机选择0-1,左闭右开的的范围,乘以数组的长度,再以Math.floor()函数进行向下取整,就能随机选到数组里的任意一个值了,取到值后也是用到系统alert()弹窗的方法进行,obj对象里languages数组以索引的方式进行弹窗选择值。

4.4 点击清除按钮会把语言全部清除

这个步骤就更简单了,点击清除按钮触发removeAllFunc我们直接将obj.languages = []置为空数组就可以了。


5、组件化开发

5.1 组件化开发

这节内容只讲组件化开发,就是采用组件化开发的思想,好处组件可以去复用,将前面我们只用jsx版本做的小项目给重构下,当然组件之间的传值,我们放在下一节来介绍,先不急,先把组件化开发的思想给了解下,这是很关键的,实际的项目都是采用组件化开发的。
在上节我们最后写了一个随机选择语言学习的小项目,不过都是将所有的东西写在了一个jsx语法里,这就必然导致了,每块代码与代码之间关联性比较强,即耦合性比较强,我们开发讲究高内聚、低耦合,那这样开发有什么好处呢,模块与模块之间代码关联性降低,利于维护。在上节我们做的小项目如下所示:
在这里插入图片描述
我们看这个网页可以将各个功能当做一个个的模块来进行引入,可以大致画为如下所示结构图:

在这里插入图片描述

接下来我们就要去先定义一个头部组件,那么在react怎么去定义一个组件呢?代码如下:

let obj = {
    "title": "今天学习什么语言?",
    "des": "大家好,我是Counterrr",
    "tips": "不忘初心,砥砺前行",
    "languages": []
}

class Header extends React.Component {
	render () {
		return <header>
            <div>{obj.title}</div>
            <div>{obj.des}</div>
            <div>{obj.tips}</div>
        </header>
	}
}

class作为es6的类的关键词,可以去创建一个类,那这个类只是一个普通的类,我们可以使用extends关键词去继承React.Component这个父类,那么它就会去创建我们这样的一个React组件,那么在我们的Header组件中必须要有一个render函数return一个jsx语法去生成我们需要渲染的模板。那么这个组件就已经创建完了,是不是很简单。还有一个需要注意的点是,组件的命名,我们组件的命名最好首字母大写,为了防止与原生的html标签冲突。
那么现在我们来看看我们创建的React这个组件要怎么去使用,使用组件的话很简单,我们将src文件夹下的app.js写成如下:

let obj = {
    "title": "今天学习什么语言?",
    "des": "大家好,我是Counterrr",
    "tips": "不忘初心,砥砺前行",
    "languages": []
}
class Header extends React.Component {
	render () {
		return <header>
                <div>{obj.title}</div>
                <div>{obj.des}</div>
                <div>{obj.tips}</div>
            </header>
	}
}
const template = (
    <div>
        <Header/>
    </div>
)

ReactDOM.render(template, document.getElementById('app'))

可以看到我们也是定义了一个常量,在里面写了jsx语法,并且将我们定义的Header组件采用标签闭合的形式写入,然后再采用ReactDOM.render( )的方法将模板渲染上id为app的元素上,此时我们可以看到页面如下:
在这里插入图片描述
跟我们之前写的头部一样。
好的将我们之前的小项目进行重构代码如下:

let obj = {
    "title": "今天学习什么语言?",
    "des": "大家好,我是Counterrr",
    "tips": "不忘初心,砥砺前行",
    "languages": []
}
class MySelectLanApp extends React.Component {
    render () {
        return <div>
            <Header></Header>
            <main>
                <ButtonActive></ButtonActive>
                <AddLang></AddLang>
                <Options></Options>
            </main>
        </div>
    }
}
class Header extends React.Component {
	render () {
		return <header>
                <div>{obj.title}</div>
                <div>{obj.des}</div>
                <div>{obj.tips}</div>
            </header>
	}
}
class ButtonActive extends React.Component {
    render () {
        return <div>
                <button>清除</button>
                <button>选择学习的语言</button>
            </div>
    }
}
class AddLang extends React.Component {
    render () {
        return  <form>
                    <div>
                        <input type="text" name="languages"></input>
                        <button>添加语言</button>
                    </div>
                </form>
    }
}
class Options extends React.Component {
    render () {
        return <ul>
                <Option/>
            </ul>
    }
}
class Option extends React.Component {
    render () {
        return <li>react</li>
    }
}

ReactDOM.render(<MySelectLanApp/>, document.getElementById('app'))

我们发现我们一共定义了6个组件,MySelectLanApp 为最外层我的随机学习语言组件,它里面有4个组件:头部组件Header 、按钮组件ButtonActive、语言添加组件AddLang 、选项组件父组件Options 。其中选项组件父组件Options 中还有一个子组件各个选择组件Option

我们可以看到在一个组件中嵌套一个子组件写法为直接将定义好的类当成标签写入进去,我们在ReactDOM.render( )渲染的时候直接去渲染最外层的我的随机学习组件,那么它就会去找里面嵌套的各个子组件去渲染,最后我们看到的页面如下:
在这里插入图片描述
此时我们已经完成了网页组件化重构,接下来讲讲组件之间的传值。


6、组件中传值

我们用react组件化开发的思想将之前的小项目重构了,最后的效果如下:
在这里插入图片描述
我们知道组件化开发最大的好处就是组件的复用,但是在我们这个小项目中Option组件即如下图:在这里插入图片描述
当我们去复用的话还是react,写死的状态,将Options组件写成如下代码:

class Options extends React.Component {
    render () {
        return (<ul>
                <Option/>
                <Option/>
                <Option/>
            </ul>)
    }
}

在这里插入图片描述
我们看到这显然不是我们想要的结果,我们要的复用是,可以去暴露接口让我们去定制想要的内容。那么在没有组件化重构之前我们是怎么去做这件事情的,我们是将对象定义在全局上,对象上有数组,去渲染这个数组,那么在我们组件化开发中直接在类render( )方法上去定义我们的数组,从而传值给我们的子组件,代码如下:

class MySelectLanApp extends React.Component {
    render () {
        let obj = {
            "title": "今天学习什么语言?",
            "des": "大家好,我是Counterrr",
            "tips": "不忘初心,砥砺前行",
            "languages": ['React', 'Vue', 'php']
        }
        return (<div>
            <Header obj={obj}></Header>
            <main>
                <ButtonActive languages={obj.languages}></ButtonActive>
                <AddLang languages={obj.languages}></AddLang>
                <Options languages={obj.languages}></Options>
            </main>
        </div>)
    }
}

可以看到我们直接在每个组件上直接写上属性,然后将值传入。接着我们在Options组件上这样去渲染:

class Options extends React.Component {
    render () {
        return (<ul>
                <Option languages={this.props.languages}/>
            </ul>)
    }
}

我们可以看到这边采用this.props.languages去获取这个数组。render函数定义在类的原型上我们去调用render里的this可以打印出组件实例对象,从而去获取绑定在实例对象上的props字段下的languages数组,紧接着我们又将这个数组以languages属性传递给了Option组件,Option组件写成如下:

class Option extends React.Component {
    render () {
        return (
            this.props.languages.map((item, index) => {
            return <li key={`option${index}`}>{item}</li>
            })
        )
    }
}

我们在Option组件里再用我们map方法去真实的渲染,页面如下:
在这里插入图片描述
Header组件代码如下:

class Header extends React.Component {
	render () {
        console.log(this)
		return (<header>
                <div>{this.props.obj.title}</div>
                <div>{this.props.obj.des}</div>
                <div>{this.props.obj.tips}</div>
            </header>)
	}
}

组件之间传值还是比较简单的,我们在组件标签上去绑定属性,然后在组件render方法上去使用this.props去获取我们自定义的属性。

7、添加事件和this丢失问题

添加事件,在没有重构之前,我们也是将事件函数定义在全局上,通过onClick去绑定这个事件函数,那我们重构后,也是将事件函数定义这个组件的类里,我们拿AddLang这个组件来写一下,这个组件也是传入languages属性并且将对象上的languages数组传入,AddLang组件代码如下:

class AddLang extends React.Component {
    submitFunc (e) {
        e.preventDefault();
        if (e.target.elements.languages.value == '') {
            alert('请输入!');
        }
        else {
            console.log(this.props.languages)
        }
    }
    render () {
        return  (<form onSubmit={this.submitFunc}>
                    <div>
                        <input type="text" name="languages"></input>
                        <button>添加语言</button>
                    </div>
                </form>)
    }
}

我们可以看到我们还是在form上去监听原生的submit事件,并且这个函数写在了AddLang这个类里,那就相当于在创建这个对象实例的时候,submitFunc 绑定在了这个原型上,那么在这个函数执行的时候this就指向这个类的实例对象。我们来点击下添加语言这个按钮看看,效果如下:
在这里插入图片描述
没问题,显示让我们输入,那我们就输入再试试:
在这里插入图片描述
我们发现报错了,Cannot read property 'props' of undefined这段代码我们熟悉的不能再属性,那这个是什么意思呢,读取不到undefined上的props属性,那为什么会这样呢,这个this不是指向这个AddLang 创建出来的实例对象吗,并且在组件传值那已经把languages给传入,那这个就是this丢失的问题了,当我们点击这个按钮,说明这个函数自执行而不是给到我们对象去调用,那么这个this指向非严格模式下就指向window,严格模式下就指向undefined,那这边走的是严格模式,那这个this就是undefined
那我们怎么去解决这个问题呢?我们可以这样写:

class AddLang extends React.Component {
    constructor(props) {
        super(props)
        this.submitFunc = this.submitFunc.bind(this)
    }
    submitFunc (e) {
        e.preventDefault();
        if (e.target.elements.languages.value == '') {
            alert('请输入!');
        }
        else {
            console.log(this.props.languages)
        }
    }
    render () {
        return  (<form onSubmit={this.submitFunc}>
                    <div>
                        <input type="text" name="languages"></input>
                        <button>添加语言</button>
                    </div>
                </form>)
    }
}

我们用到这个类里constructor构造函数,在这个类实例化的时候就是去执行这段代码,我们将submitFunc 函数的指向用bind函数给改变了,对于bind这块可以查看浅谈JavaScript的函数的call( ) apply( ) bind( )我的这篇博客。这样的话我们输入再去点击:
在这里插入图片描述
就会把this.props.languages给打印出来。

在学习的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你华还是你华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值