本篇博客为本人学习react的入门记录笔记,参考资料来之柴柴老师在b站分享的react学习视频,仅供学习参考,学习视频是来自于b站的:柴柴_前端教书匠
目录
打开命令行窗口 ,输入命令:(如果是使用vscode开发工具的,记得要以管理员的身份打开这个vs code):
npx create-react-app react-basic
前提:想要成功执行这个命令,需要自己的电脑中安装了这个node的环境。
创建好的项目的结构:把src文件夹下没有用到的文件删除,只保留下面几个主要的文件:
最后app.js与index.js的代码如下(进行了删除)
- function App() {
- return (
- "App">
- app
-
- );
- }
-
- export default App;
- import React from 'react'; //框架的核心包
- import ReactDOM from 'react-dom/client'; //专门做渲染相关的包
- import './index.css'; //应用的全局样式文件
- import App from './App'; //引入根组件,一切组件化的开始
-
- //开始渲染根组件 渲染到哪里去?渲染到一个id为 root 的dom节点上 实际上打开这个public中的index.html文件中就可以看见一个div的id为root,
- //然后进行挂载,大致的流程就是 自动的对root根组件进行渲染挂载
- const root = ReactDOM.createRoot(document.getElementById('root'));
- root.render(
-
- //把严格模式节点需要去掉,不然在这个模式下会影响我们的useEffect的时机,这个模式会检查额外的副作用会让我们的useEffect执行两次,这个是react18的特性
- //
-
- //
- );
启动项目:或者是使用命令启动 : npm run start 或者是 yarn start
然后就会打开浏览器显示如下:
JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构
作用:在React中创建HTML结构(页面UI结构)
优势:
采用类似于HTML的语法,降低学习成本,会HTML就会JSX
充分利用JS自身的可编程能力创建HTML结构
注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法
语法:{ JS 表达式 } 注意这个{}里面只能写表达式,不能写语句!!!
可以使用的表达式:
字符串、数值、布尔值、null、undefined、object( [] / {} )
1 + 2、'abc'.split('')、['a', 'b'].join('-')
fn()
案例演示:在创建的react项目中的app.js文件中写的;
- //1.识别我们的常规变量
- const name = "react 学习"
-
- //2.原生js中的调用
- const getAge = () =>{
- return "永远的18";
- }
-
- //3.三元运算符(常用)
- const flag = false;
-
- function App() {
- return (
- "App">
- {name}
- {getAge()}
- {flag ? '18':'68'}
-
- );
- }
-
- export default App;
特别注意:if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {}
中!!
react中如何完成列表的渲染? 技术方案:map
- //4.创建一个列表 重复渲染的是哪个模板 就return谁,比如我们这里渲染的是一个列表,所以就return一个列表
- //注意事项:遍历的列表同时需要一个类型为nuber/string 不可重复key 提高diff性能
- //key不仅仅在内部使用,不会出现在真实的dom结构中
- const songs = [
- { id: 1, name: '痴心绝对' },
- { id: 2, name: '像我这样的人' },
- { id: 3, name: '南山南' }
- ]
- function App() {
- return (
- "App">
-
- {
- //重复渲染的是哪个模板就return谁, 比如我们这里渲染的是一个列表,所以就return一个列表
- songs.map(item =>
- {item.name}
) - }
-
-
- );
- }
- export default App;
key 在 HTML 结构中是看不到的,是 React 内部用来进行性能优化时使用
key 在当前列表中要唯一的字符串或者数值(String/Number)
如果列表中有像 id 这种的唯一值,就用 id 来作为 key 值
如果列表中没有像 id 这种的唯一值,就可以使用 index(下标)来作为 key 值
作用:根据是否满足条件生成HTML结构,满足条件才进行渲染。
实现:可以使用 三元运算符
或 逻辑与(&&)运算符
- // 来个布尔值
- const flag = true
- function App() {
- return (
- "App">
- {/* 条件渲染字符串 */}
- {flag ? 'react真有趣' : 'vue真有趣'}
- {/* 条件渲染标签/组件 */}
- {flag ? (this is span) : null}
-
- )
- }
- export default App
如果我们遇到了 复杂的多分支的逻辑,那么应该怎么办呢?肯定是不能三元套三元的,这样会导致代码的可读性非常弱。
原则:模板中的逻辑尽量保持精简。 收敛为一个函数,通过一个专门的函数来写分支逻辑,模板中只负责调用。
- const getTag = (type)=>{
- if(type === 1){
- return
this h1
- }
- if(type === 2){
- return
this h2
- }
- if(type === 3){
- return
this h1
- }
- }
-
- function App() {
- return (
- "App">
- getTag(1)
-
- );
- }
-
- export default App;
注意:使用样式处理的时候,需要使用两个{}来进行包裹,第一个{}的作用是为了让第二个{}识别为对象,第二个{}是对象,用来写我们的样式属性。
1、行内样式 - style (在元素身上绑定一个style属性即可)
- function App() {
- return (
- "App">
- 'red' }}>this is a div
-
- )
- }
- export default App
2、行内式 - style - 更优写法 (把样式以对象抽离出来)
- const styleObj = {
- color:red
- }
- function App() {
- return (
- "App">
- this is a div
-
- )
- }
- export default App
3、类名 - className(推荐)(在元素身上绑定一个className属性即可)
- .active{
- color:blue;
- }
- import './app.css'
- function App() {
- return (
- "App">
- 'active'>测试类名样式
-
- )
- }
- export default App
样式文件:命名为app.css
- .active{
- color:blue;
- }
- import './app.css'
- const flag = true;
- const style = {
- color:'red'
- }
- function App() {
- return (
- "App">
- 'active' : ''}>测试动态类名
- 测试行内样式
-
- );
- }
-
- export default App;
1. JSX必须有一个根节点,如果没有根节点,可以使用<>>
(幽灵节点)替代
2. 所有标签必须形成闭合,成对闭合或者自闭合都可以
3. JSX中的语法更加贴近JS语法,属性名采用驼峰命名法 class -> className
for -> htmlFor
4. JSX支持多行(换行),如果需要换行,需使用()
包裹,防止bug出现
安装vsCode prettier插件
修改配置文件 setting.json
- {
- "git.enableSmartCommit": true,
- // 修改注释颜色
- "editor.tokenColorCustomizations": {
- "comments": {
- "fontStyle": "bold",
- "foreground": "#82e0aa"
- }
- },
- // 配置文件类型识别
- "files.associations": {
- "*.js": "javascript",
- "*.json": "jsonc",
- "*.cjson": "jsonc",
- "*.wxss": "css",
- "*.wxs": "javascript"
- },
- "extensions.ignoreRecommendations": false,
- "files.exclude": {
- "**/.DS_Store": true,
- "**/.git": true,
- "**/.hg": true,
- "**/.svn": true,
- "**/CVS": true,
- "**/node_modules": false,
- "**/tmp": true
- },
- // "javascript.implicitProjectConfig.experimentalDecorators": true,
- "explorer.confirmDragAndDrop": false,
- "typescript.updateImportsOnFileMove.enabled": "prompt",
- "git.confirmSync": false,
- "editor.tabSize": 2,
- "editor.fontWeight": "500",
- "[json]": {},
- "editor.tabCompletion": "on",
- "vsicons.projectDetection.autoReload": true,
- "editor.fontFamily": "Monaco, 'Courier New', monospace, Meslo LG M for Powerline",
- "[html]": {
- "editor.defaultFormatter": "vscode.html-language-features"
- },
- "editor.fontSize": 16,
- "debug.console.fontSize": 14,
- "vsicons.dontShowNewVersionMessage": true,
- "editor.minimap.enabled": true,
- "emmet.extensionsPath": [
- ""
- ],
- // vue eslint start 保存时自动格式化代码
- "editor.formatOnSave": true,
- // eslint配置项,保存时自动修复错误
- "editor.codeActionsOnSave": {
- "source.fixAll": true
- },
- "vetur.ignoreProjectWarning": true,
- // 让vetur使用vs自带的js格式化工具
- // uni-app和vue 项目使用
- "vetur.format.defaultFormatter.js": "vscode-typescript",
- "javascript.format.semicolons": "remove",
- // // 指定 *.vue 文件的格式化工具为vetur
- "[vue]": {
- "editor.defaultFormatter": "octref.vetur"
- },
- // // 指定 *.js 文件的格式化工具为vscode自带
- "[javascript]": {
- "editor.defaultFormatter": "vscode.typescript-language-features"
- },
- // // 默认使用prettier格式化支持的文件
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "prettier.jsxBracketSameLine": true,
- // 函数前面加个空格
- "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
- "prettier.singleQuote": true,
- "prettier.semi": false,
- // eslint end
- // react
- // 当按tab键的时候,会自动提示
- "emmet.triggerExpansionOnTab": true,
- "emmet.showAbbreviationSuggestions": true,
- "emmet.includeLanguages": {
- // jsx的提示
- "javascript": "javascriptreact",
- "vue-html": "html",
- "vue": "html",
- "wxml": "html"
- },
- // end
- "[jsonc]": {
- "editor.defaultFormatter": "vscode.json-language-features"
- },
- // @路径提示
- "path-intellisense.mappings": {
- "@": "${workspaceRoot}/src"
- },
- "security.workspace.trust.untrustedFiles": "open",
- "git.ignoreMissingGitWarning": true,
- "window.zoomLevel": 1
- }
错误提示的插件:
组件化开发是当前前端开发的主流模式。
组件开发中,每一个组件都是一个独立的功能,可以各个组件被重复利用,维护起来也可以在对应的位置进行维护。
理解:组件实际上就是把一个一个的小功能抽离封装起来,然后像搭积木一样,把这些组件叠加起来,最后形成了一个大的应用,这就是所谓的组件。
react提供了两种组件,一种是函数组件,一种是类组件。
函数组件的创建:
- //创建函数式组件
- function Hello(){
- return hello
- }
- function App () {
- return (
- "App">
- {/* 渲染hello组件,相当于在这里进行了调用 */}
-
-
- )
- }
- export default App
约定说明
组件的名称必须首字母大写,react内部会根据这个来判断是组件还是普通的HTML标签。
函数组件必须有返回值,返回的值表示该组件的 UI 结构;如果不需要渲染任何内容,则返回 null。
组件就像 HTML 标签一样可以被渲染到页面中。组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的返回值就是对应的内容。
使用函数名称作为组件标签名称,可以成对出现也可以自闭合。
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- return hello
- }
-
- //类组件的创建和渲染
- class HelloCompoent extends React.Component {
- //这个render函数是必须的
- render () {
- return this is class component
- }
- }
- function App () {
- return (
-
- "App">
- {/* 渲染hello组件 */}
-
-
- {/* 渲染类组件 */}
-
-
- )
- }
- export default App
约定说明
类名称也必须以大写字母开头
类组件应该继承 React.Component 父类,从而使用父类中提供的方法或属性
类组件必须提供 render 方法render 方法必须有返回值,这个返回值表示该组件的 UI 结构
语法 on + 事件名称 = { 事件处理程序(回调函数) } ,比如:
注意点 react事件采用驼峰命名法,比如:onMouseEnter、onFocus
函数事件绑定:
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- //创建一个事件(相当于一个方法)
- const clickHandler = () => {
- console.log('函数组件中的事件被触发了')
- }
- //绑定事件
- return hello
- }
-
- function App () {
- return (
- "App">
- {/* 渲染hello组件 */}
-
-
- )
- }
-
- export default App
类组件事件绑定:
- import React from "react"
-
- //类组件的创建和渲染
- class HelloCompoent extends React.Component {
-
- //事件回调函数(标准写法) 这样写可以避免this指向问题 这样写可以回调函数中的this指向是当前的组件实例对象,如果你不这样写的话是很容易出现this指向不明的
- clickHandler2 = () => {
- console.log('类组件中的事件被触发了')
- }
-
- //这个render函数是必须的
- render () {
- //在类组件中绑定回调函数,需要使用this关键字来进行调用 this指向当前组件实例
- return this is class component
- }
- }
- function App () {
- return (
-
- "App">
- {/* 渲染类组件 */}
-
-
- )
- }
-
- export default App
事件对象e可以用来阻止一些的默认行为。
函数组件获取e对象方式如下:直接在形参中添加e就行:
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- //创建一个事件(相当于一个方法)
- const clickHandler = (e) => { //函数组件中获取e对象
- console.log('函数组件中的事件被触发了', e)
- }
- return hello
- }
- function App () {
- return (
-
- "App">
- {/* 渲染hello组件 */}
-
-
- )
- }
-
- export default App
使用e对象阻止一些默认行为,我们先来看一下什么是默认行为:
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- //创建一个事件(相当于一个方法)
- const clickHandler = (e) => {
- console.log('函数组件中的事件被触发了', e)
- }
- return
- }
-
- //类组件的创建和渲染
- class HelloCompoent extends React.Component {
-
- //事件回调函数(标准写法)
- clickHandler2 = () => {
- console.log('类组件中的事件被触发了')
- }
-
- //这个render函数是必须的
- render () {
- //在类组件中绑定回调函数,需要使用this关键字来进行调用
- return this is Compoent
- }
- }
-
- function App () {
- return (
-
- "App">
- {/* 渲染hello组件 */}
-
-
- {/* 渲染类组件 */}
-
-
- )
- }
-
- export default App
点击上面的百度:控制台会打印下面的文字并且页面进行跳转:
我们自己的打印信息被百度的信息进行了覆盖,并且网页也进行了跳转,我们只想让自己的信息打印出来不想跳转,那么如何操作?
再次点击 百度,看控制台的输出信息,并且发现页面也不会进行跳转了。
场景:就是我们想在触发事件的时候,传递一些自定义参数,应该怎么操作?
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- //创建一个事件(相当于一个方法)
- const clickHandler = (msg) => {
- console.log('函数组件中的事件被触发了', msg)
- }
- //需要在调用的时候传递实参,需要修改函数组件触发的调用方式了 : 改造成箭头函数的调用方式就行
- return clickHandler('this is msg')}> 点击
- {/*之前的调用方式点击 */}
- }
- function App () {
- return (
-
- "App">
- {/* 渲染hello组件 */}
-
-
- )
- }
- export default App
如果我们想要既传递 e 又要想要传递自定义参数,那么应该怎么操作?
- import React from "react"
-
- //创建函数式组件
- function Hello () {
- //创建一个事件(相当于一个方法)
- const clickHandler = (e,msg) => {
- console.log('函数组件中的事件被触发了', e,msg)
- }
- //需要在调用的时候传递实参,需要修改函数组件触发的调用方式了 : 改造成箭头函数的调用方式就行 并且需要注意形参和实参的顺序
- return clickHandler(e,'this is msg')}> 点击 {/* 需要先捕获e然后再传递到绑定的事件中*/}
- {/*
- 之前不需要传递参数的调用方式点击
- 之前传递一个自定义参数的调用方式:return clickHandler('this is msg')}> 点击
- */}
- }
- function App () {
- return (
-
- "App">
- {/* 渲染hello组件 */}
-
-
- )
- }
- export default App
总结:
1、只需要一个额外参数 写法: {clickHandler} -> { () => clickHandler('自定义参数')}
2、既需要e也需要额外的自定义参数 {(e) => clickHandler(e,'自定义的参数') }
一个前提:在React hook出来之前,函数式组件是没有自己的状态的,所以这里我们先统一通过类组件来进行讲解。
下面演示一下:初始化状态,获取状态中的值,修改状态中的值。
下面的写法可以避免this的指向问题,以后在使用react的开发中直接这样写就行,不需要去维护this的指向问题。
- import React from "react"
-
- //组件状态 这里使用类组件作为演示
- class TestComponent extends React.Component {
-
- //1.定义组件状态 【初始化状态】
- state = {
- //这里面可以定义各种属性 这些属性全是当前组件的状态
- name: '组件状态测试'
- }
-
- //5.声明事件回调函数(自定义的)
- changeName = () => {
- //在事件回调函数中修改组件状态 【修改状态】注意:这里不可以直接做赋值修改 必须通过setState方法来进行修改这个state值
- this.setState({
- //直接在这个方法中修改state的属性
- name: '修改后的name'
- })
- }
-
- //这个render函数是组件自己携带的方法 3.在render方法中写一些处理逻辑
- render () {
- return (
-
- this is TestComponent
- {/*【读取状态】*/}
- 当前组件的name属性为:{this.state.name}
- {/*4.定义按钮触发自定义事件 */}
-
-
- )
- }
- }
-
- function App () {
- return (
-
- "App">
- {/* 2.渲染定义的类组件 */}
-
-
- )
- }
-
- export default App
总结:
1、编写组件其实就是编写原生的js类或者是函数
2、定义状态必须通过state实例属性中的方法提供一个对象 名称是固定的就叫做state
3、修改状态state中的任何属性 都不可以通过直接赋值来进行修改,必须走setState方法 这个方法来自于继承得到的(因为react体系中,'数据不可变')
4、这里的this关键词很容易出现指向错误的问题,上面的案例写法是最推荐最规范的,是不会出现this的指向问题
5、当然上面这样来使用状态是比较麻烦的,后面学习了hook就用的都是函数式组件了,但是一些老的项目维护的应该还是类组件,所以这里也是需要学习一下的。
练习: 写一个count按钮,点击按钮实现按钮中间的数据加一
- class Counter extends React.Component {
- // 定义数据
- state = {
- count: 0
- }
- // 定义修改数据的方法
- setCount = () => {
- this.setState({
- {/* 不能通过++来完成,必须要拿到上一次的值(this.state.count) 然后对其进行处理(+1处理) 再赋值给这个属性 */}
- count: this.state.count + 1
- })
- }
- // 使用数据 并绑定事件
- render () {
- return
- }
- }
这里我们作为了解内容,随着js标准的发展,主流的写法已经变成了class fields,无需考虑太多this问题
下面演示一种错误的写法:
- import React from "react"
- class Test extends React.Component {
-
- change(){
- console.log(this) //输出的this是undefined 所以此时再去使用this.setState修改数据就会报错
- }
-
- render () {
- return (
-
-
-
- )
- }
- }
-
- function App () {
- return (
- "App">
- {/* 渲染定义的类组件 */}
-
-
- )
- }
- export default App
如果你非要 change(){}这种写法来定义函数式组件,那么要怎么才能解决这个this指向问题?如下:
- import React from "react"
- class Test extends React.Component {
- //在几年之前的react的修正this指向,就是经常这么操作的---手动修正
- constructor(){
- super();
- //使用bind强行修正我们的this指向 相当于在类组件的初始化阶段就可以把【回调函数的this】修正到永远指向当前实例对象
- this.change = this.change.bind(this);
- }
-
- change(){
- console.log(this) //输出的this是组件的实例属性
- }
-
- render () {
- return (
-
-
-
- )
- }
- }
-
- function App () {
- return (
- "App">
- {/* 渲染定义的类组件 */}
-
-
- )
- }
- export default App
另一种解决这种this指向的问题:
- import React from "react"
- class Test extends React.Component {
-
- change(){
- console.log(this) //输出的this是组件的实例属性
- }
-
- render () {
- return (
-
- {/*如果不通过constructor做修正,那么我们可以直接在事件绑定的位置通过箭头函数的写法 --> 这种写法是直接沿用父函数(render)中的this 这个函数的父函数是render,这也说明了render中的this指向的就是【当前组件】的实例对象(react内部进行了修正) */}
-
-
- )
- }
- }
-
- function App () {
- return (
- "App">
- {/* 渲染定义的类组件 */}
-
-
- )
- }
- export default App
建议:在开发中定义函数式组件的时候直接就 handle = () => {} 来定义就行,然后引用的时候直接通过 this.handle来使用,参考count按钮加数操作中的写法。
目标:能够理解不可变的意义并且知道在实际开发中如何修改状态。
概念:不能直接修改状态(state)的值,而是基于当前状态创建新的状态值。
1、错误的直接修改
- state = {
- count : 0,
- list: [1,2,3],
- person: {
- name:'jack',
- age:18
- }
- }
- // 直接修改简单类型Number 这种直接修改状态是不行的
- this.state.count++
- ++this.state.count
- this.state.count += 1
- this.state.count = 1
-
- // 直接修改数组 这种直接修改状态是不行的
- this.state.list.push(123)
- this.state.list.spice(1,1)
-
- // 直接修改对象 这种直接修改状态是不行的
- this.state.person.name = 'rose'
2、 基于当前状态创建新值(正确的修改)
- this.setState({
- count: this.state.count + 1
- list: [...this.state.list, 4], //拿到原有的数据然后再把它赋值给一个新的值
- person: {
- ...this.state.person,
- // 覆盖原来的属性 就可以达到修改对象中属性的目的
- name: 'rose'
- }
- })
扩展:如何删除数组中的元素? 使用fileter函数
- this.setState({
- list: this.state.list.filter(item => item !== 2) //把原list中的2给过滤,就是相当于删除了2(这里面还可以写其他过滤条件)
- })
目标: 能够使用受控组件的方式获取文本框的值
使用React处理表单元素,一般有俩种方式:
受控组件 (推荐使用)
非受控组件 (了解)
什么是受控组件? input框自己的状态被React组件状态控制
(input框的状态指的就是它的value属性,value是啥它显示的就是啥,同时用户输入的新内容也会被存到这个value值中,所以说input框的状态指的就是这个value属性),react的状态就是我们之前学习的state,所以受控组件指的就是你input的value属性被state中的数据控制了。
React组件的状态的地方是在state中,input表单元素也有自己的状态是在value中,React将state与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证【单一数据源特性】。
以获取文本框的值为例,受控组件的使用步骤如下:
在组件的state中声明一个组件的状态数据
将状态数据设置为input标签元素的value属性的值
为input添加change事件,在事件处理程序中,通过事件对象e获取到当前文本框的值(即用户当前输入的值
)
调用setState方法,将文本框的值作为state状态的最新值
代码实现:
vue中的v-model数据双向绑定的原理就是下面这么回事:
- import React from 'react'
-
- class InputComponent extends React.Component {
- // 1、声明组件状态 在状态中声明一个属性用来控制input的value属性
- state = {
- message: 'this is message',
- }
- // 声明事件回调函数
- changeHandler = (e) => {
- //4、拿到输入框的最新值 把它交给state中的message 那么怎么拿到这个value?--->通过e对象
- this.setState({ message: e.target.value })
- }
- render () {
- return (
-
- {/* 2、绑定value 绑定事件 把输入框的value属性绑定状态中的属性 3、绑定事件回调函数*/}
-
-
- )
- }
- }
-
-
- function App () {
- return (
- "App">
-
-
- )
- }
- export default App
下面的内容了解就行:
非受控组件:就是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件的state中的状态控制,直接通过原生dom获取输入框的值。
导入createRef
函数
调用createRef函数,创建一个ref对象,存储到名为msgRef
的实例属性中
为input添加ref属性,值为msgRef
(第二部存储的值的名字)
在按钮的事件处理程序中,通过msgRef.current
即可拿到input对应的dom元素,而其中msgRef.current.value
拿到的就是文本框的值(current是固定的属性)
- import React, { createRef } from 'react'
-
- class InputComponent extends React.Component {
- // 使用createRef产生一个存放dom的对象容器
- msgRef = createRef()
-
- changeHandler = () => {
- console.log(this.msgRef.current.value)
- }
-
- render() {
- return (
-
- {/* ref绑定 获取真实dom */}
-
-
-
- )
- }
- }
-
- function App () {
- return (
- "App">
-
-
- )
- }
- export default App