if
…else
… 声明式设计只需要告诉我要实现什么样的,具体做法React来帮你做,这也是因为React使用事件委托代理的方法处理UI渲染导致我们无法直接操作指定DOM元素,交给React自行处理具体安装配置环境我就不做过多的解释了,因为官网写的清清楚楚
我们直接来使用
import React from "react"
import ReactDOM from "react-dom/client"
)
// 自react 18之后会和之前的方法不太一样:
const rootApp = document.querySelector("#root")
ReactDOM.createRoot(rootApp).render(<section id="div" className="div_box">nihao</section>)
首先root
节点是必须要获取的,因为我们的组件是要渲染到root
节点中的,那么,root
节点是什么?为什么我们要渲染到root
节点中?
让我们来看看下面这张图,你就明白了 😁
我们看到react中的唯一html文件body节点之中只有root这样一个节点,我们之后的组件都会添加到该root节点中
其实原理是这样的 😐
ReactDOM.createRoot(document.querySelector("#root")).render(
React.createElement(
"section",
{
id: "div",
className: "div_box",
},
"nihao"
)
)
等等,为什么HTML元素直接可以写到js文件中使用且不报错???🤔
其实这是JSX语法,有兴趣的可以专门去了解一下,总之 ------ 好用就行😁
大家一定会问,我感觉这样更乱了,并没有什么所谓的组件化啊🤐
我们可以用ES6的import
语法导入外部模块啊:
import App from "./base_01/test_01"
const rootApp = document.querySelector("#root")
ReactDOM.createRoot(rootApp).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
现在我已经导入了外部js文件了呀,那么这个js文件我该怎么写呢?
// 创建组件类时必须导入react核心模块
import React from "react"
class App extends React.Component {
render() {
return (
<section>
<p>Hello React Component</p>
</section>
)
}
}
// 向外共享
export default App
如果你接触过node,那么这些你很容易理解
还有一种更爽的写法,我们可以把共享和创建连写,并且不需要继承React的Component类,我们可以使用import语法直接将react的component类导入:
import React, { Component } from "react"
export default class App extends Component {
render() {
return (
<section>
<h1>React很有趣 -- 你也来一起玩吧!</h1>
</section>
)
}
}
COOL!
等等,我有一个奇妙的想法,这样是否可以呢?😶:
import React, { Component } from "react"
class Test extends Component {
render() {
return <p>你好呀</p>
}
}
export default class App extends Component {
render() {
return (
<section>
<h1>React很有趣 -- 你也来一起玩吧!</h1>
<Test></Test>
</section>
)
}
}
OK,这样写完全可以(这就是组件的嵌套)
我相信大家知道这是ES6的class类,那么我们可不可以使用函数组件呢?它们又有什么区别呢?
import React from "react"
function App2() {
return (
<section>
<div>GOOD!函数式组件</div>
</section>
)
}
// 函数式组件无状态组件(16.8之前)
// 16.8之后,有了react hooks -- 有状态性
export default App2
所以说函数组件本身是无状态的,比如我们去操作DOM的时候,react函数式组件不会像类组件一样使用this.setState方法去渲染局部UI数据,那么函数式组件就不能使用了吗?并不是,我们还可以使用hooks,可以让react函数式组件具有状态性😊,不过这都是后话了,这篇文章是初识react,所以我们作为简单了解就好
函数式写起来真的很爽,配合我们的箭头函数,真的飞起😋:
const Test = () => <div>Hello</div>
我们会插入HTML文档了,整体架构出来了,那么样式我们怎么写呢?
react推荐我们使用行内式,那么我们先来看看外部CSS文件导入该怎么写:
import "./CSS/test_04.css" // 导入CSS模块 -- webpack的支持(将css文件以style标签的形式插入到文档中)
class Headerbar extends Component {
render() {
return (
<div>
<input type="text" id="username"></input>
</div>
)
}
}
这很简单,不需要过多阐述,我们来看看行内式该怎么写:
class Headerbar extends Component {
render() {
const user_name = "barve-airpig"
// 类中定义对象供样式使用
let styleObj = {
background: "yellow",
}
// react模板字符串{} VUE的插值表达式是双花括号:{{ }}
// 原生写法:`${ }`
return (
<div style={styleObj}>
Headerbar {20 + 30} -- {user_name} -- {20 - 20 || "Hello"}
<p style={{ fontWeight: "bold" }}>你好</p>
<div className="test_box">模块导入外部CSS文件有用吗?</div>
<label htmlFor="username">用户名:</label>
<input type="text" id="username"></input>
</div>
)
}
}
这些都很好理解,唯一使我们困惑的就是:htmlFor、className是什么属性,虽然知道这是什么意思,但是react中样式属性都要重新记一遍吗?这可有点不妙啊
并不是这样,只是部分属性需要我们注意,这也是避免一些问题而修改的
终于到了初识react的重头戏了,架构、样式、行为,我们都会了,那么我们的react就入门了
import React, { Component } from "react"
export default class App extends Component {
render() {
return (
<section>
// 方法一:使用箭头函数,点击事件触发事件回调执行代码
<button
onClick={() => {
console.log("Bye addEventListener")
}}
>
点击按钮1
</button>
// 方法2:点击事件触发事件回调执行当前继承类APP中的handleClick方法(handleClick是普通函数)
// 注意这里只有点击才会执行对应函数
<button
onClick={() => {
this.handleClick()
}}
>
点击按钮2
</button>
// 方法3:点击执行对应处理函数,不过不要加小括号,否则函数会自动执行,下次执行也无效
<button onClick={this.handleClick}>点击按钮3</button>
// 方法4:点击执行对应箭头函数
<button onClick={this.handleClick2}>点击按钮3</button>
</section>
)
}
handleClick() {
console.log("你好!😁")
}
handleClick2 = () => {
console.log("react真好玩!")
}
}
大家看到我列出了四个版本,没有太大的区别,一定想到了我想要说什么,没错,就是this指向问题:
以上代码输出结果为:
这当然没有什么问题,但是我们想要知道的是this指向
import React, { Component } from "react"
export default class App extends Component {
render() {
return (
<section>
<input />
<button
onClick={() => {
console.log(this)
}}
>
点击按钮1
</button>
<button
onClick={() => {
this.handleClick()
}}
>
点击按钮2
</button>
<button onClick={this.handleClick}>点击按钮3</button>
<button onClick={this.handleClick2}>点击按钮3</button>
</section>
)
}
handleClick() {
console.log(this)
}
handleClick2 = () => {
console.log(this)
}
}
等等,谁是undefined????
没错,按钮三的this去执行的函数中找this,但是没想到这是一个普通函数,this没有指向,而其他的都是箭头函数,箭头函数this指向是最近父作用域链的this,那么当然是继承类APP了
不行,我今天就要用这种方法,那么有没有解决方法😡,哈哈哈,当然有,你还记得我们的ES6 – bind()方法吗,修改this值且不会自动执行,用在这里再合适不过了:
<button onClick={this.handleClick.bind(this)}>点击按钮3</button>
React 事件绑定和原生事件绑定有什么区别呢?
react没有获取DOM节点-也有事件对象吗?
event
事件对象,这个对象和普通的浏览器event
对象所包含的方法和属性都基本不一致,不同的是 react 中的event
对象并不是浏览器提供的,而是它自己内部所构建的,它同样具有event.stopPropagation
阻止冒泡以及event.preventDefault
阻止默认行为等等这种常用的方法相信学习过Vue的同学们对于ref指令并不陌生,其实再react中功能很相像啊
export default class App extends Component {
render() {
return (
<section>
<input ref={"mytext"} />
{/* 避免变量名重复 -- 不建议自己写ref指令 -- refs将被弃用 */}
<button
onClick={() => {
console.log("Bye addEventListener", this.refs.mytext.value)
}}
>
点击按钮1
</button>
</section>
)
}
}
给元素一个ref属性值来挂钩,这样的做法对于Vue来说司空见惯了,不过这种方法快被弃用了(react与vscode都提醒我了,我很害怕😂),所以作为了解就好
接下来才是真正的新方法:
对了,不推荐上面的写法是因为避免ref指令值重复导致不必要的错误
export default class App extends Component {
// 让react为我们创建一个ref值,我们指定名称就好
myRef = React.createRef()
render() {
return (
<section>
// 使用ref值
<input ref={this.myRef} />
<button onClick={() => this.handleClick()}>点击按钮1</button>
</section>
)
}
handleClick = () => {
// 这种获取方式很特殊啊,this.myRef.current.value
if (this.myRef.current.value.trim()) console.log(this.myRef.current.value)
}
}
上面我们提到一个非常疑惑的点:
【这种获取方式很特殊啊,this.myRef.current.value】
为什么要使用这种方法获取DOM元素呢???
这很好解释,让我们倒推myRef的属性 :
console.log(this.myRef.current.value, this.myRef)
现在你知道为什么要使用current
了吧,current中的input才是真正的input元素,底层实现我们之后再去讨论
我们都知道在react开发中,我们应该尽量减少DOM操作,那么我们需要改变UI数据,就需要依靠状态
状态是什么?它是一个数据载体,就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同,也就是数据更改,视图自动更新
我想大家应该知道Vue的数据驱动视图思想吧,react没有和Vue一样去底层拦截数据,感知不到数据的改变,所以react使用状态来改变数据
this.state是纯JavaScript对象 - 在Vue中,data属性是利用Object。defineProperty处理过的,更改data的数据会触发数据的getter和setter,但是react中没有做过这样的处理,如果直接更改的话,react是无法得知的,所以需要使用特殊的方法更改状态setState
废话不多说,开整代码:
import React, { Component } from "react"
export default class App extends Component {
// 定义状态值
state = {
show: true,
}
render() {
return (
<section>
<h1>React很有趣 -- 你也来一起玩吧!</h1>
<button onClick={() => this.likeFunc()}>
{this.state.show ? "收藏" : "取消收藏"}
</button>
</section>
)
}
likeFunc = () => {
// 我们不能和VUE一样直接修改data值以达到数据驱动视图的效果,我们可以使用setState方法,让react为我们自行修改
this.setState({
show: !this.state.show,
})
// 间接修改state状态
}
}
这样我们就可以随心所欲的点击更改UI数据了
你一定会说,我直接原生写都比这方便,这有什么用???
我们不如再往后看看react的美妙,它的组件化,它的性能优化… 😉
当后端给我们返回一堆数据我们该怎么循环渲染到UI页面上??
这是一个非常重要的问题 😎
但是这同样离不开我们的状态,因为循环渲染必定会用到我们的DOM,如果我们使用原生开发的话,那么你会怎么做?取到数据,使用模板字符串,然后创建元素,循环渲染到UI界面?等等,react不需要我们去自己操作,交给react吧,我们只需要把自己的需求告诉react 😊
首先让我们定义状态 – list数组中是我们取回来的数据:
import React, { Component } from "react"
export default class App extends Component {
constructor() {
super()
this.state = {
// list: ["学习Vue", "学习react", "摆烂"],
list: [
{ id: 1, text: "学习Vue" },
{
id: 2,
text: "学习react",
},
{
id: 3,
text: "摆烂",
},
],
}
}
}
然后呢?forEach?还是?好了,不买关子了,我们最好使用map映射,为什么?让我们往下看 😁:
import React, { Component } from "react"
export default class App extends Component {
constructor() {
super()
this.state = {
// list: ["学习Vue", "学习react", "摆烂"],
list: [
{ id: 1, text: "学习Vue" },
{
id: 2,
text: "学习react",
},
{
id: 3,
text: "摆烂",
},
],
}
}
render() {
// 列表中必须加key值
const newList = this.state.list.map((item, i) => (
<li key={item.id}>
{i}
{item.text}
</li>
))
return (
<section>
<h1>Hello -- Developer!</h1>
<ul>{newList}</ul>
</section>
)
}
}
COOL!炫酷到我不知道该从哪里看起,首先我们map映射出JSX语法的标签,将每一项的索引以及数据动态创建渲染到UI界面,帅呆了 😍
当然,我发现了其中的一个问题,比如:
这就要提到我们的虚拟DOM了
什么是虚拟DOM?就是根据状态,react会生成一份虚拟DOM,与真实DOM进行比较,如果状态被更改了,那么虚拟DOM也将更改,使用diff算法进行比较,对比前后修改对应的key值相同的数据,避免了错误更改与性能优化
假如不涉及到列表的增加删除、重排等,设置成索引也是可以的
import React, { Component } from 'react';
export default class App extends Component {
iptRef = React.createRef();
linkRef = React.createRef();
constructor() {
super();
this.state = {
todolist: [{ id: 1, todolistText: '你好 -- 欢迎使用ToDoList' }],
};
}
render() {
return (
<section>
<input ref={this.iptRef} />
<button onClick={e => this.addList()}>添加</button>
<ul>
{this.state.todolist.map((list, i) => (
<li key={list.id}>
{/* {list.todolistText} */}
{/* 富文本 */}
<span
dangerouslySetInnerHTML={{ __html: list.todolistText }}
></span>
<a
href="/#"
ref={this.linkRef}
key={this.iptRef}
onClick={e => this.removeList(e, i)}
>
删除
</a>
</li>
))}
</ul>
{this.state.todolist.length === 0 && <div>暂无待办事件</div>}
</section>
);
}
addList = () => {
if (this.iptRef.current.value.trim()) {
// 不建议直接修改状态
// const newList = this.state.todolist.slice();
const newList = [...this.state.todolist];
newList.push({
id: newList.length + 1,
todolistText: this.iptRef.current.value.trim(),
});
this.setState({
todolist: newList,
});
this.iptRef.current.value = '';
}
};
removeList = (e, i) => {
// 在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为, 你必须明确使用 preventDefault
e.preventDefault();
const nowLinkList = [...this.state.todolist];
nowLinkList.splice(i, 1);
this.setState({
todolist: nowLinkList,
});
};
}
import React, { Component } from 'react';
import './CSS/选项卡.css';
import HOME from './选项卡component/HOME';
import ABOUT from './选项卡component/ABOUT';
import TALK from './选项卡component/TALK';
import MINE from './选项卡component/MINE';
export default class App extends Component {
listRef = React.createRef();
constructor() {
super();
this.state = {
buttonList: [
{
id: 1,
buttonText: 'HOME',
},
{
id: 2,
buttonText: '关于',
},
{
id: 3,
buttonText: '聊天',
},
{
id: 4,
buttonText: '我的',
},
],
current: 0,
};
}
render() {
return (
<section>
{this.state.current === 0 && <HOME></HOME>}
{this.state.current === 1 && <ABOUT></ABOUT>}
{this.state.current === 2 && <TALK></TALK>}
{this.state.current === 3 && <MINE></MINE>}
<div>
<ul>
{this.state.buttonList.map((list, i) => (
<li
ref={this.listRef}
className={this.state.current === i ? 'active' : ''}
key={list.id}
onClick={() => this.hihgLight(i)}
>
{list.buttonText}
</li>
))}
</ul>
</div>
</section>
);
}
hihgLight = index => {
this.setState({
current: index,
});
};
}