写的东西太多了,照成csdn文档编辑器都开始卡顿了,所以分篇写。
1.安装React
需要安装下面三个包。
react:react核心包
react-dom:渲染需要用到的核心包
babel:将jsx语法转换成React代码的工具。(没使用jsx可以不装)
1.1 在html中引入CND
在html中使用react是一种简便的方式。非常有利于初学者学习。
地址为:https://zh-hans.react.dev/learn/installation
需要引入下面这三个CDN
注意:这三个顺序是不能变的
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
1.2 React初体验
1.2.1 JSX写法(掌握)
在html里面添加下面的代码。注意一定要添加type="text/babel"不然是会不识别语法的。
<body>
<div id="root"></div>
<script type="text/babel">
//1.创建虚拟DOM
const VDOM=<h1>Hello React</h1>
//或
const VDOM = (//不加小括号换行也是不会报错的,仅仅是为了结构好管理
<h1>
<span>Hello React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('root'));
</script>
</body>
1.2.2 React原始写法(了解)
下面这种写法可以不使用babel,也就不用引用babel包,所以类型也可以写成type=“text/javascript”。
这是react提供的语法糖,react自己的原始代码都是这种形式写的。对于开发者来说,这种语法是非常难用的。JSX就是react为我们提供的方便使用的语法。
下面的写法不用手动写,开发就用JSX,但是React会把JSX转化成下面的写法,了解一下还是很有必要的。
<body>
<div id="root"></div>
<div id="root2"></div>
<script type="text/javascript">
//1.创建虚拟DOM
const VDOM = React.createElement('h1',{id:"title"},"Hello React")
//嵌套多层的时候非常的麻烦
const VDOM2 = React.createElement('h1',{id:"title"},React.createElement('span',{},"Hello React"))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('root'));
ReactDOM.render(VDOM2, document.getElementById('root2'));
</script>
</body>
1.2.3组件写法(后面掌握)
这种写法可以先跳过,后面学组件的时候再说。
<div id="root"></div>
<script type="text/babel">
function MyApp() {//这可以理解为一个组件
return <h1>Hello, world!</h1>;//不要奇怪,这是jsx语法,就是支持这样写的
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);//18以前这个函数名字叫render
root.render(<MyApp />);
</script>
运行一下,就有hello world了。
上面的代码可以这样写,实现一样的效果。
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);//18以前这个函数名字叫render
root.render(<h1>Hello, world!</h1>);
1.3 真实DOM和虚拟DOM的区别
我们把虚拟DOM直接输出,可以看到虚拟DOM就是一个普通的js对象。
const VDOM = React.createElement('h1', { id: "title" }, "Hello React")
console.log("虚拟DOM:",VDOM);
真实DOM可以通过下面的方式输出。
const realDOM=document.getElementById("root")
console.log("真实DOM:",realDOM);
可以看到在浏览器直接把内容给输出了。
这时候我们需要debugger断点来看看他的真实内容是怎么样的。
可以看到,真实DOM是有非常多的属性的,而虚拟DOM的属性实际上非常的少。
或者使用console.dir也是可以直接输出的。g
console.dir(realDOM);
小结:
1.虚拟DOM本质上是一个普通对象。
2.虚拟DOM比较轻,真实DOM比较重,因为react不需要那么多属性。
3.虚拟DOM最终被react转化为真实DOM,呈现在页面上。
2.JSX语法规则
1.定义虚拟DOM时,不要写引号
const VDOM=<h1>Hello React</h1>
2.标签中引入JS表达式需要用{}
不需要加引号,直接跟{}
const myId="title"
const myData="Hello World"
const VDOM = (
<h1 id={myId}>
<span>{myData}</span>
</h1>
)
3.不允许写class这个属性,而要写className
这是因为class是一个ES6类的关键词,React为了避开这个,自己定义了className。
.title{
background:orange
}
//直接跟类名
<h1 className="title">Hello React</h1>
4.style不能写成字符串的形式,而是要写成对象的形式
需要大括号包裹。注意下面的{{}}并不是类似vue的mustache语法,而是和变量外面要使用{}是一样的道理,里面嵌套的{}表示的是这是一个对象。
<h1 style={{color:'lightblue',fontSize='29px'}}>Hello React</h1>
5.虚拟DOM只能有一个根元素
6.标签必须闭合
<input type="text" />
或者
<input type="text"><input/>
7.标签首字母
(1).若小写字母开头,则将该标签转为html同名元素,若html中无该标签对应的同名元素,则报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
注意: 你写的这些div标签都是JSX标签,不是html标签,或者说JSX给你提供了类似html的语法,最终他会转变成html标签在浏览器展示。
3.循环列表
注意,循环只能使用map这种表达式,不可以用for循环,因为for循环是语句。
{}里面是只能写js表达式,不能写js语句,所以for和if判断都是不可以写的。
这里必须需要理解清楚表达式和语句的区别
表达式: 表达式会产生一个值,这个值可以放在任意一个需要值的地方。
a
a+b
foo(1)
arr.map()
console.log("name")
function test(){}//方法也是表达式,也是有返回值的
语句(代码):
if(){}
for(){}
switch(){}
<body>
<div id="root"></div>
<script type="text/babel">
const data = ['Angular', 'React', 'Vue']
const VDOM = (
<div>
<h1>遍历列表</h1>
<ul>
{
data.map((item,index)=>{//这种方式实现key是不太合适的,后面再说
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('root'));
</script>
</body>
4.组件编程
4.1 react浏览器插件安装
推荐使用edge浏览器。下载方便,注意需要把下面两个勾打上,不然本地路径图标是不会亮的。
f12刷新一下开发者工具可以看到Components和Profiler这两个功能模块。
4.2 函数式组件
需要注意的是函数名字需要大写,不然报错。render函数的第一个参数需要写成标签的形式,不然报错。
<div id="root"></div>
<script type="text/babel">
function MyComponent(){
return <div>我是一个函数式组件(适用于简单组件的定义)</div>
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
4.2.1 函数式组件this的指向
this指向是undefined,因为react在被babel翻译的时候开启了严格模式。
4.3 类式组件
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
render(){
return <div>这是一个类式组件(用于复杂的场景)</div>
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
1.react解析MyComponent标签,找到MyComponent组件。
2.发现组件是类定义的,new出该类的实例,通过实例调用render方法。
3.将render方法返回的虚拟DOM转化为真实DOM,在浏览器上呈现。
render里面的this是组件实例对象。
5.组件实例三大属性之state
简单使用
props必写,不写报错。继承必须调用构造器,这是class规定的。直接通过this.state赋值就可以了。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:false}
}
render(){
return <div>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
回调事件
通过onClick指定点击函数。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:false}
}
render(){
return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
}
function changeWeather(){
console.log("被点击了");
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
修改state的值
点击事件虽然有了,但是如果我们想要通过下面的代码来修改state的值的话是会报错的。因为this是undefined。
function changeWeather(){
console.log("被点击了");
this.state.isHot=false
}
我们可以定义一个that变量,在构造器里面把this赋值给that,这样就可以在外面获取到组件实例对象了。
<div id="root"></div>
<script type="text/babel">
let that
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:true}
that=this
}
render(){
return <div onClick={changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
}
function changeWeather(){
console.log("被点击了",that);
// that.state.isHot=false
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
这种方式虽然可行,但是非常的不合理。因为写的代码都是零散的,没有组件的封装性。
试着把代码改成下面这个样子。这里存在非常多的问题。
1.onClick={this.changeWeather},需要写this.changeWeather而不是直接写changeWeather。这是因为现在changeWeather是写在类里面,而写类外面是直接调用。
2.render和constructor的this是组件的实例对象,而changeWeather的this却是undefined。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:true}
}
render(){
return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
changeWeather(){
console.log("被点击了",this);
// that.state.isHot=false
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
正确的做法: (没搞懂)
添加this.changeWeather=this.changeWeather.bind(this)这一行就可以了。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:true}
this.changeWeather=this.changeWeather.bind(this)
}
render(){
return <div onClick={this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
changeWeather(){
console.log("被点击了",this);
// that.state.isHot=false
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
也可以写成下面这个样子:(没搞懂)
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:true}
this.demo=this.changeWeather.bind(this)
}
render(){
return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
changeWeather(){
console.log("被点击了",this);
// that.state.isHot=false
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
通过setState修改值
直接修改值是没有响应式的,值虽然修改成功了,但没有响应式。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component{
constructor(props){//props必写,不写报错
super(props)//继承必须调用构造器,这是class规定的
this.state={isHot:true}
this.demo=this.changeWeather.bind(this)
console.log(this);
}
render(){
return <div onClick={this.demo}>今天天气很{this.state.isHot?"炎热":"凉爽"}</div>
}
changeWeather(){
console.log("被点击了",this);
// this.state.isHot=!this.state.isHot
this.setState({isHot:!this.state.isHot})
}
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<MyComponent/>, document.getElementById('root'));
</script>
setState这个方法是来自React.Component这个类的,可以通过原型链查看到。
setState会合并对象而不是替换对象。也就是可以指定属性进行修改。
state的精简写法(最正确的写法 )
1.首先,利用class的特性,写在类里面的属性会直接挂载到类实例对象上。构造器可以不用写,直接定义一个state属性就可以。
2.每定义一个点击回调函数都要在构造器里面写类似this.changeWeather=this.changeWeather.bind(this)这样的代码,非常的麻烦。可以利用箭头函数,会自动向上查找this的特性完美的处理this指向问题。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return <div onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</div>
}
changeWeather = () => {
this.setState({ isHot: !this.state.isHot })
}
}
ReactDOM.render(<MyComponent />, document.getElementById('root'));
</script>
小结
state是三大属性中最难最重要的。掌握了state,最难的骨头已经啃下来了。
6.组件实例三大属性之props
基本使用
非常简单,没什么问题。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
</ul>
</div>
)
}
changeWeather = () => {
this.setState({ isHot: !this.state.isHot })
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
批量传递props
写成下面这样就可以用对象来批量传递属性了。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
</ul>
</div>
)
}
changeWeather = () => {
this.setState({ isHot: !this.state.isHot })
}
}
const p={name:"Tom",age:18}
ReactDOM.render(<MyComponent {...p} />, document.getElementById('root'));
</script>
细节:
这里有个细节需要讲一下。{…p}并不是展开对象。
在纯js里面,下面这样的写法是错误的,对象是不能通过…展开的,数组可以,需要是可迭代对象。
const p={name:"Tom",age:18}
console.log(...p);
在纯js里面,下面的写法是正确的,这是es6创建对象的一种方式。相当于复制对象。
const p={name:"Tom",age:18}
const p2={...p}
console.log(p2);
但如果是在react标签里面,{}是react的表达式符号,不是对象,…p是不能展开对象的,按照js的语法的话,但在react里面可以,这是babel和react帮我们实现的。
<MyComponent {...p} />
在react里面写下面的代码,不会报错,但也没内容,这是react做了处理的结果。
const p={name:"Tom",age:18}
console.log(...p);
props类型限制
在react15之前是不需要额外引用包的。
是直接写在react包里面的,后面抽离了出去。基本没人用React15了。也不会有这种写法了。
MyComponent.propTypes = {
name: React.PropTypes.string
}
新写法:react16以后
需要引入这个包。
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
类型定义和默认值,没什么好说的。
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func
}
MyComponent.defaultProps = {
name: "Tom",
age: 19
}
props的精简写法
把原本写在外面的写到类里面,并加上static就行了。
加static相当于直接挂载到类本身上,而不是实例对象上。
class MyComponent extends React.Component {
state = { isHot: true }
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func
}
static defaultProps = {
name: "Tom",
age: 19
}
render() {
return (
<div>
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
</ul>
</div>
)
}
changeWeather = () => {
this.setState({ isHot: !this.state.isHot })
}
}
构造器和props
构造器可以不写,但是写了必须写props和super,不然会出现在构造器里面调用this.props出现undefined的bug。这个官方文档里面明确写的。
constructor(props){
super(props)
console.log(this.props);//不写和不传super,this.props是undefiend
}
这种情况是非常少见的,几乎不会出现。一是你不用写构造器也可以在别的地方使用this.props,二是如果你写了构造器,那么参数的props是可以直接使用的,不需要使用this.props。
函数式组件中使用props
在函数式组件里面,我们只能玩玩props,state和refs是玩不了的,因为没有this。而且props之所以能玩也是因为函数能传参数这个特性。类型限制只能写在外面。
<div id="root"></div>
<script type="text/babel">
function Person(props) {
return (
<div>
<ul>
<li>姓名:{props.name}</li>
<li>年龄:{props.age}</li>
</ul>
</div>
)
}
//类型限制只能写在外面
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func
}
Person.defaultProps = {
name: "Tom",
age: 19
}
const p = { name: "Tom", age: 18 }
ReactDOM.render(<Person name="Jack" age="20" />, document.getElementById('root'));
</script>
7.组件实例三大属性之refs
7.1 基本使用(字符串类型的ref)
通过下面的代码使用基本功能:
1.点击按钮显示输入框内容。
2.输入框焦点离开,显示输入框内容。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<input id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
<input id="input2" placeholder="" onBlur={this.showData2} />
</div>
)
}
showData = () => {
const input=document.getElementById("input1")
alert(input.value)
}
showData2 = () => {
const input=document.getElementById("input2")
alert(input.value)
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
上面代码是通过原生getElementById实现的,不太合适。我们希望通过react实现。react的refs就可以实现类似的功能。
react的refs是用来给元素打标识用的。
修改后的代码如下,非常的简单。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<input ref='input1' id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
<input ref='input2' id="input2" placeholder="" onBlur={this.showData2} />
</div>
)
}
showData = () => {
// const input=document.getElementById("input1")
const {input1}=this.refs
alert(input1.value)
}
showData2 = () => {
// const input=document.getElementById("input2")
const {input2}=this.refs
alert(input2.value)
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
注意:这里获取到的refs的内容是真实DOM节点的内容。
我们输出this看下refs长什么样子。是一个键值对,key是ref的名字,value是元素的类型加元素的id。
没有写id的话,我都不知道后面会跟id。
7.2 回调形式的ref
字符串形式的ref是最简单的一种ref。但是官方不推荐使用了,因为存在效率问题。不过如果你用的是16.8版本的话,继续用也没什么大问题,毕竟用起来简单。
下面是回调函数的用法,个人觉得不好用,写起来冗余和麻烦。
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
console.log(this);
return (
<div>
<input ref={e=> this.input1 = e} id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
<input ref={e=> this.input2 = e} id="input2" placeholder="" onBlur={this.showData2} />
</div>
)
}
showData = () => {
const { input1 } = this
alert(input1.value)
}
showData2 = () => {
const { input2 } = this
alert(input2.value)
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
7.3 回调形式ref的调用次数问题
这个在官方文档里面有提到,在写内联函数的情况下,第一次渲染的时候会调用一次,当页面刷新的时候,会调用两次,第一次内联函数的参数值是null(被重置了),第二次是正常值。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
<input ref={e => { this.input1 = e; console.log("@:", e); }} id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
</div>
)
}
showData = () => {
this.setState({
isHot: !this.state.isHot
})
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
写成绑定函数的形式就不会出现这种问题了,但这个问题是无关紧要的。下面的这种写法非常的麻烦,完全没有必要。 用内联回调函数的形式是完全没有问题的。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
render() {
return (
<div>
<span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
<input ref={this.saveInput} id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
</div>
)
}
showData = () => {
this.setState({
isHot: !this.state.isHot
})
}
saveInput = (e)=>{
this.input1=e
console.log("@:",e);
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
7.4 通过createRef创建ref (推荐)
这种方式是官方推荐的,用起来不是很麻烦,也解决了性能问题。不过最简单还是字符串的形式。不过发生性能问题就不好说了。字符串形式还是不要使用比较好。
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = { isHot: true }
myRef1=React.createRef()
myRef2=React.createRef()
render() {
return (
<div>
<span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</span>
<input ref={this.myRef1} id="input1" placeholder="请输入内容" />
<button onClick={this.showData}>点击显示内容</button>
<input ref={this.myRef2} onBlur={this.showData2} id="input1" placeholder="请输入内容" />
</div>
)
}
showData = () => {
alert(this.myRef1.current.value)
}
showData2 = () => {
alert(this.myRef2.current.value)
}
}
ReactDOM.render(<MyComponent name="Tom" age="18" />, document.getElementById('root'));
</script>
react脚手架使用和搭建
后面的有些内容不用脚手架建立工程项目的话使用起来不是很方便。或者不知道怎么使用,也不想花费大量的时间研究可能用不上的使用方式。所以用工程项目还是最有性价比的选择。
手动搭建react项目
通过下面的命令创建一个脚手架项目:
npm install -g create-react-app
create-react-app react-demos
默认目录结构如下:
这些内容可以作为参考,但是现在我们把src下的内容全部删掉,因为我们想要重头搭建我们的项目。
这时候我们再运行会告诉我们没有index.js。这是因为webpack需要index.js作为程序的入口。
非常神奇的是,你只要添加一个空白index.js文件,项目就可以运行的,只不过页面是空白的。
我们往index.js添加下面的内容,其中#root是index.html中的根元素,webpack自己会取找。
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<h1>Hello React</h1>);
跑起来我们就可以看到下面的内容。
我们可以定义一个App类组件来封装我们的内容。
import ReactDOM from "react-dom/client";
import React from "react";
class App extends React.Component {
render() {
return <h1>Hello React</h1>;
}
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
也可以把App组件单独写到外面。
import React from "react";
class App extends React.Component {
render() {
return <h1>Hello React</h1>;
}
}
export default App;
import ReactDOM from "react-dom/client";
import React from "react";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
我们可以定义自己的组件,然后在App.jsx里面引入
import React from "react";
class HelloReact extends React.Component {
render() {
return <h1>Hello React</h1>;
}
}
export default HelloReact;
这样,我们的工程就搭建完成了。
import React from "react";
import HelloReact from "./HelloReact";
class App extends React.Component {
render() {
return (
<div>
<HelloReact />
</div>
);
}
}
export default App;
通过eject命令查看webpack配置
我们可以看到react脚手架创建的项目实际上是通过react-scripts这个工具来运行的。而webpack的配置就在这个包里面的。
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
可以看到webpack的配置在这个包下面。
react-scripts这个工具提供了eject命令来暴露webpack的配置,但是一般不推荐这么做。一是因为webpack的配置非常复杂且难懂,二是一般我们专注开发,不要花太多精力在这些东西上。
一定要查看和修改也是有办法的,执行下面的命令:
npm run eject
会弹出警告,问你要不要弹出配置文件,这个操作是不可逆的。
执行完后,会多出config和scripts两个文件夹。
scripts节点的内容也变成下面的内容。
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},
整个package.json都变了。依赖什么都一股脑的写道里面,非常的乱。所以还是不要弹出配置比较好。
使用craco来管理配置
这是一个第三方工具。主流的工具,大家都在用。
组件的生命周期
生命周期是非常重要的内容,我们需要在正确的生命周期做正确的事情。
这么这个网站是react官方声明周期图的地址,16.8后,react在官方文档里面已经移除了到这个图的导航。
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
这个图有两个版本,一个简单版,一个显示不常用生命周期版本。如果只看简单版的话,react的生命周期还是比较简单的。
不常用生命周期:
组件挂载和componentDidMount方法
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log("constructor");
}
render() {
console.log("render");
}
componentDidMount() {
console.log("compoentDidMount");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
组件先执行constructor,再执行render,再执行componentDidMount。
父子组件的componentDidMount方法
如果是父子组件的话,情况就复杂一点。
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log("constructor");
}
render() {
console.log("render");
return <SubComponent />;
}
componentDidMount() {
console.log("compoentDidMount");
}
}
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("sub constructor");
}
render() {
console.log("sub render");
}
componentDidMount() {
console.log("sub compoentDidMount");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
父组件的componentDidMount是最后执行的。但父组件是先被挂载的。之所以会出现父组件的componentDidMount后执行是因为使用的是深度优先遍历算法。
更新数据时的生命周期和componentDidUpdate方法
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
message: "this is message",
};
constructor(props) {
super(props);
console.log("constructor");
}
changeMessage = () => {
this.setState({
message: "message changed",
});
};
render() {
console.log("render");
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.changeMessage}>changeMessage</button>
</div>
);
}
componentDidMount() {
console.log("compoentDidMount");
}
componentDidUpdate() {
console.log("compoentDidUpdate");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
更新数据的时候,render函数会被多次调用。并且再render执行完成后,都会调用componentDidUpdate方法。
更新数据时的生命周期和componentDidUpdate方法——子组件更新
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log("constructor");
}
render() {
console.log("render");
return (
<div>
<SubComponent />
</div>
);
}
componentDidMount() {
console.log("compoentDidMount");
}
componentDidUpdate() {
console.log("compoentDidUpdate");
}
}
class SubComponent extends React.Component {
state = {
message: "this is message",
};
constructor(props) {
super(props);
console.log("sub constructor");
}
changeMessage = () => {
this.setState({
message: "message changed",
});
};
render() {
console.log("sub render");
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.changeMessage}>changeMessage</button>
</div>
);
}
componentDidMount() {
console.log("sub compoentDidMount");
}
componentDidUpdate() {
console.log("sub compoentDidUpdate");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
可以看到。当子组件内容被修改的时候,只会调用,子组件的更新方法,父组件是不会调用更新方法的,也不会调用render。
更新数据时的生命周期和componentDidUpdate方法——父组件更新
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
message: "this is message",
};
constructor(props) {
super(props);
console.log("constructor");
}
changeMessage = () => {
this.setState({
message: "message changed",
});
};
render() {
console.log("render");
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.changeMessage}>changeMessage</button>
<SubComponent />
</div>
);
}
componentDidMount() {
console.log("compoentDidMount");
}
componentDidUpdate() {
console.log("compoentDidUpdate");
}
}
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("sub constructor");
}
render() {
console.log("sub render");
}
componentDidMount() {
console.log("sub compoentDidMount");
}
componentDidUpdate() {
console.log("sub compoentDidUpdate");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
父组件内容修改的时候,子组件的render和componentDidUpdate也是会一起调用的,也就是子组件会重新渲染,因为子组件可能依赖了父组件的数据。react选择直接重新渲染子组件。
组件卸载和componentWillUnmount方法
在组件被销毁的时候,componentWillUnmount会被调用。我们需要在componentWillUnmount做一些回收资源的事情。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
show: true,
};
constructor(props) {
super(props);
// console.log("constructor");
}
changeShow = () => {
this.setState({
show: false,
});
};
render() {
// console.log("render");
const { show } = this.state;
return (
<div>
{show && <SubComponent />}
<button onClick={this.changeShow}>changeShow</button>
</div>
);
}
}
class SubComponent extends React.Component {
state = {
show: true,
};
constructor(props) {
super(props);
console.log("sub constructor");
}
changeShow = () => {
this.setState({
show: false,
});
};
render() {
console.log("sub render");
return <div>我是子组件</div>;
}
componentDidMount() {
console.log("sub compoentDidMount");
}
componentDidUpdate() {
console.log("sub compoentDidUpdate");
}
componentWillUnmount() {
console.log("sub componentWillUnmount");
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
不常用的生命周期
不常用也是官方文档说的。
shouldComponentUpdate方法
如果shouldComponentUpdate返回false,那么组件的render和componentDidUpdate将不会被调用。这个可以用于一些性能优化的地方。禁止一些组件的重新渲染。
从图中也可以很明显看到,render和componentDidUpdate这两个函数是在shouldComponentUpdate之后执行的,如果shouldComponentUpdate返回false,那么后面的生命周期将不再执行。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
message: "this is message",
};
constructor(props) {
super(props);
console.log("constructor");
}
changeMessage = () => {
this.setState({
message: "message changed",
});
};
render() {
console.log("render");
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.changeMessage}>changeMessage</button>
</div>
);
}
componentDidMount() {
console.log("compoentDidMount");
}
componentDidUpdate() {
console.log("compoentDidUpdate");
}
shouldComponentUpdate() {
return true;
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
getsnapshotbeforeupdate方法
官方文档给了下面的例子。
https://zh-hans.legacy.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
处理列表滚动的时候可以用到,是伪代码。
<body>
<div id="root"></div>
<script type="text/babel">
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return <div ref={this.listRef}>{/* ...contents... */}</div>;
}
}
ReactDOM.render(<ScrollingList />, document.getElementById("root"));
</script>
</body>
组件通信
组件通信父传子props
这个其实没什么好讲的,直接使用props就可以了
组件通信子传父
在vue或者uniapp里面,子传父有类似$emit,$on这样的发送接收方法。但在react里面,这些都是没有的。react实现的方式就是纯js的方式,是利用回调函数机制来实现的。
我们需要在子组件上加一个回调方法。
<AddCounter onAdd={(n) => this.handleAdd(n)} />
完整逻辑如下。这里需要注意的地方就是函数的命名规范。例如add方法,你可以在父子组件里面都全部取名叫add,如果你自己能够分得清的话。
命名规范建议是,点击事件用clickXXX,组件回调函数用onXXX,而父组件处理函数用handleXXX。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
counter: 0,
};
constructor(props) {
super(props);
console.log("constructor");
}
handleAdd = (num) => {
this.setState({
counter: this.state.counter + num,
});
};
render() {
const { counter } = this.state;
return (
<div>
<div>计数结果是:{counter}</div>
<AddCounter onAdd={(n) => this.handleAdd(n)} />
</div>
);
}
}
class AddCounter extends React.Component {
constructor(props) {
super(props);
console.log("constructor");
}
clickAdd = (n) => {
this.props.onAdd(n);
};
render() {
return (
<div>
{/*要写箭头函数,不能直接执行 直接执行会在在渲染的时候就调用了 */}
<button onClick={() => this.clickAdd(1)}>+1</button>
<button onClick={() => this.clickAdd(5)}>+5</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
tabs切换的组件通信案例(简单实现版)
写在一个组件里面,实现基本功能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
.tabs {
display: inline-block;
padding: 10px;
}
.active {
color: pink;
border-bottom: 2px solid pink;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
tabs: [
{
name: "电脑",
},
{
name: "手机",
},
{
name: "电视",
},
],
curIndex: 0,
};
clickTab = (index) => {
this.setState({
curIndex: index,
});
};
constructor(props) {
super(props);
console.log("constructor");
}
render() {
const { tabs, curIndex } = this.state;
return (
<div>
{tabs.map((item, index) => {
return (
<span
className={`tabs ${index === curIndex ? "active" : ""}`}
key={index}
onClick={(e) => this.clickTab(index)}
>
{item.name}
</span>
);
})}
<div>{tabs[curIndex].name}</div>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
</html>
tabs切换的组件通信案例(组件封装版)
PureComponent类和memo高阶函数
这个类是React提供的封装好的优化类。只有组件的props或者state发生改变的时候,才会重新渲染组件。源码实现是通过浅层比较实现,也就是只比较对象的第一层。
对于函数式组件来说,是没有生命周期的,也就不能实现类似PureComponent这样的封装效果,但是React提供了memo函数来实现这个功能。
开发的时候,你是一定要用到这两个东西的,不知道的话你一定是一个新手。官方文档性能优化是有提到这两个东西的。
hooks
什么是hooks? (coderwhy)
hooks是react 16.8(2019年)出的新特性。
react有两种形式来创建组件——类式和函数式。在hooks之前类式组件就是react最主流的编程方式。 这个时候,函数式组件是非常鸡肋的,几乎没什么用。因为函数式组件不能保存数据状态,所以只能用于一些简单的展示的场景,传什么数据就展示什么数据(因为只有props是可以用的)。并且函数组件是没有生命周期的。
但因为函数式编程的思想在前端是普遍流行和推崇的。我猜想react团队在设计函数式组件的时候肯定已经想到了这个问题。但可能当时没有想到合适方式实现函数式组件的完整功能。
对于开发者来说,使用类式组件或者函数式组件来开发功能实际上都是无所谓,谁好用就用谁。但设计者为了实现函数式组件可以说是绞尽脑汁。至于设计出来的东西好不好用另说。但函数式组件的这条路是一定要走下去的。
还有一个促使hooks诞生的原因是类式组件存在一些缺点。例如类式不好对功能进行拆分。当然hooks本身是否存在别的缺点我们另说。class概念难以理解以及this的指向问题处理对于初学者来说都是比较麻烦的。
1.hooks是完全可选的,你不用hooks,用类式组件也是完全没有问题的。
2.hooks是100%向后兼容的。hook不包含任何破坏性改动。
3.hooks的代码比类组件相对少一些。
组件插槽
react没有slot这样的节点概念,因为他不需要。
通过props.children实现插槽效果
通过props.children就可以直接实现插槽的效果。
<body>
<div id="root"></div>
<script type="text/babel">
class NavBar extends React.Component {
state = { isHot: true };
render() {
const { children } = this.props;
return (
<div className="content">
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
);
}
changeWeather = () => {
this.setState({ isHot: !this.state.isHot });
};
}
NavBar.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func,
};
NavBar.defaultProps = {
name: "Tom",
age: 19,
};
class Main extends React.Component {
render() {
return (
<NavBar>
<div>我是左边内容</div>
<div>我是中间内容</div>
<div>我是右边内容</div>
</NavBar>
);
}
}
ReactDOM.render(<Main />, document.getElementById("root"));
</script>
</body>
是可以实现插槽效果的。
但是,如果只有一个元素的时候,children并不是一个数组,而是一个元素对象,这时候通过数组下标取元素就不对了,虽然控制台并不会报错。这是react就是这样设计的,不要问为什么。 存在弊端。
<NavBar>
<div>我是左边内容</div>
{/*
<div>我是中间内容</div>
<div>我是右边内容</div>
*/}
</NavBar>
props.children方式存在的问题:
1.children一会是数组一会是对象,容易出错。
2.children是通过下标来直接取元素的,可读性非常的差,容易取错。
通过props参数实现插槽效果
直接通过参数把元素内容传过去,是不是非常的神奇?
再react里面,直接在props里面传参数就可以实现插槽的效果。参数名称随便取。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<style>
.content {
display: flex;
/* align-items: center;
justify-content: space-around; */
height: 50px;
.left {
width: 100px;
background: pink;
}
.center {
width: 100%;
background: lightblue;
}
.right {
width: 100px;
background: lightgreen;
}
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
class NavBar extends React.Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props;
console.log(this.props);
return (
<div className="content">
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
);
}
}
class Main extends React.Component {
render() {
return (
<NavBar
leftSlot={<div>我是左边内容</div>}
rightSlot={<div>我是右边内容</div>}
centerSlot={<div>我是中间内容</div>}
/>
);
}
}
ReactDOM.render(<Main />, document.getElementById("root"));
</script>
</body>
</html>
受控组件和非受控组件
受控组件本质上就是表单组件,在react中表单数据交由state管理。这是react自己创造的一个概念。
非受控组件在react中也是指表单组件,只是数据不是state控制的,用户提供直接操作dom来管理表单数据。几乎是不会用的,只是相当于受控组件的一个概念。
受控组件
下面的代码有一个非常重要的特点是,在设置了value的值后,浏览器的输入框是不能再输入内容的,也不能修改内容。这是被react作用到的。正常的html设置了input的value内容还是可以编辑的。
还有一个小细节是label的绑定id的属性是htmlFor,原来是for,这是因为for是关键字。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return (
<div>
<label htmlFor="name">
姓名:
<input id="name" value="Tom" />
</label>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
给input添加onChange事件,这就是受控组件了。我们在state中绑定了value值,这就是react的受控组件的概念了。实际就是给input组件实现了数据绑定。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
username: "Tom",
};
handleChange(e) {
console.log(e.target.value);
this.setState({
username: e.target.value,
});
}
render() {
return (
<div>
<label htmlFor="name">
姓名:
<input
id="name"
value={this.state.username}
onChange={(e) => this.handleChange(e)}
/>
</label>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
表单提交行为和受控组件
下面的代码就是传统表单的提交方式。 这种方式现在已经没人用了。下面的代码在提交表单的时候会刷新页面。
<form action="/adduser">
<label htmlFor="name">
姓名:
<input
id="name"
value={this.state.username}
onChange={(e) => this.handleChange(e)}
/>
</label>
<button type="submit">提交</button>
</form>
react的表单提交方式:
下面的代码先是取消了action属性。通过拦截onSubmit方法来实现表单的提交。核心的逻辑就是handleSubmit方法。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
username: "Tom",
};
handleSubmit(event) {
console.log(event);
//1.阻止默认行为
event.preventDefault();
//2.获取控件数据内容
console.log(this.state.username);
//3.通过axios提交给服务器
}
handleChange(event) {
console.log(event.target.value);
this.setState({
username: event.target.value,
});
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="name">
姓名:
<input
id="name"
value={this.state.username}
onChange={(e) => this.handleChange(e)}
/>
</label>
<button type="submit">提交</button>
</form>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
多个受控组件使用同一个函数处理
其实就是一个处理技巧。通过event.target.name来动态获取属性名,当然,需要我们指定元素name属性的值。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
username: "Tom",
password: "123",
};
handleSubmit(event) {
console.log(event);
//1.阻止默认行为
event.preventDefault();
//2.获取控件数据内容
console.log(this.state.username, this.state.password);
//3.通过axios提交给服务器
}
handleChange(event) {
console.log(event.target.name);
this.setState({
[event.target.name]: event.target.value,
});
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="name">
姓名:
<input
id="name"
name="username"
value={this.state.username}
onChange={(e) => this.handleChange(e)}
/>
</label>
<label htmlFor="name">
密码:
<input
id="password"
type="password"
name="password"
value={this.state.password}
onChange={(e) => this.handleChange(e)}
/>
</label>
<button type="submit">提交</button>
</form>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
单个checkbox和多个checkbox
单个checkbox
我们实现一个登录页面的代码,有用户名密码和同意协议。需要注意的是,checkbox使用的属性是checked不是value。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
username: "Tom",
password: "123",
isAgree: false,
};
handleSubmit(event) {
console.log(event);
//1.阻止默认行为
event.preventDefault();
//2.获取控件数据内容
const { username, password, isAgree } = this.state;
console.log(username, password, isAgree);
//3.通过axios提交给服务器
}
handleChange(event) {
console.log(event.target.name);
this.setState({
[event.target.name]: event.target.value,
});
}
handleCheckedChange(event) {
this.setState({
isAgree: event.target.checked,
});
}
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="name">
姓名:
<input
id="name"
name="username"
value={this.state.username}
onChange={(e) => this.handleChange(e)}
/>
</label>
<br />
<label htmlFor="name">
密码:
<input
id="password"
type="password"
name="password"
value={this.state.password}
onChange={(e) => this.handleChange(e)}
/>
</label>
<br />
<label htmlFor="isAgree">
<input
id="isAgree"
type="checkbox"
checked={this.state.isAgree}
onChange={(e) => this.handleCheckedChange(e)}
/>
用户协议
</label>
<button type="submit">提交</button>
</form>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
多个checkbox
多个checkbox主要也是表单的一些选项,这是非常常见的。需要注意的是。我们需要定义一个数组来管理多个checkbox的状态。
<body>
<div id="root"></div>
<script type="text/babel">
class MyComponent extends React.Component {
state = {
username: "Tom",
password: "123",
isAgree: false,
hobbies: [
{ name: "唱", value: "sing", checked: false },
{ name: "跳", value: "dance", checked: false },
{ name: "rap", value: "rap", checked: false },
],
};
handleSubmit(event) {
console.log(event);
//1.阻止默认行为
event.preventDefault();
//2.获取控件数据内容
const { username, password, isAgree, hobbies } = this.state;
const selectedHobbies = hobbies
.filter((item) => item.checked)
.map((item) => item.value);
console.log(username, password, isAgree);
console.log(selectedHobbies);
//3.通过axios提交给服务器
}
handleChange(event) {
console.log(event.target.name);
this.setState({
[event.target.name]: event.target.value,
});
}
handleCheckedChange(event) {
this.setState({
isAgree: event.target.checked,
});
}
handleHobbiesChange(event, index) {
const hobbies = [...this.state.hobbies];
hobbies[index].checked = event.target.checked;
this.setState({
hobbies,
});
}
render() {
const { username, password, isAgree, hobbies } = this.state;
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="name">
姓名:
<input
id="name"
name="username"
value={username}
onChange={(e) => this.handleChange(e)}
/>
</label>
<br />
<label htmlFor="password">
密码:
<input
id="password"
type="password"
name="password"
value={password}
onChange={(e) => this.handleChange(e)}
/>
</label>
<br />
{hobbies.map((item, index) => {
return (
<label htmlFor={item.value} key={index}>
<input
id={item.value}
type="checkbox"
checked={item.checked}
onChange={(e) => this.handleHobbiesChange(e, index)}
/>
{item.name}
</label>
);
})}
<br />
<label htmlFor="isAgree">
<input
id="isAgree"
type="checkbox"
checked={isAgree}
onChange={(e) => this.handleCheckedChange(e)}
/>
用户协议
</label>
<br />
<button type="submit">提交</button>
</form>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
高阶函数和高阶组件
高阶函数回顾
什么是高阶函数。一个函数接收一个函数作为参数或者返回一个函数,那个这个函数就是高阶函数。
高阶组件(High Order-Component HOC)
这是react官方给的定义。
官方定义: 高阶组件是参数为组件,返回值为新组件的函数。
注意: 虽然叫高阶组件,但实际上是函数。
接收一个组件作为他的参数。
高阶组件简单例子
高阶组件的主要意义就是对传入的组件进行拦截,这样,我们就可以对传入的组件做一些额外操作了。
下面的代码就是一个简单的高阶组件,虽然没有什么实际意义。
<body>
<div id="root"></div>
<script type="text/babel">
// import { PureComponent } from "react";
class FooComponent extends React.PureComponent {
render() {
return <div></div>;
}
}
function enhanceComponent(OldComponent) {
class NewComponent {
render() {
return <OldComponent name="commonName" />;
}
}
return NewComponent;
}
ReactDOM.render(<FooComponent />, document.getElementById("root"));
const newComponent = enhanceComponent(FooComponent);
console.log(newComponent);
</script>
</body>
高阶组件的应用场景一:props
下面的代码可以给每个组件注入一个userInfo到各自的props里面。我们需要把自身的props也传进去。
还是有点用的,如果你需要给某些组件插入一些测试数据的话。
<body>
<div id="root"></div>
<script type="text/babel">
// import { PureComponent } from "react";
class MyComponent extends React.PureComponent {
render() {
return (
<div>
<Home />
<Profile />
<Find />
</div>
);
}
}
const Home = enhanceComponent(function (props) {
return <div>Home {props.name}</div>;
});
const Profile = enhanceComponent(function (props) {
return <div>Profile {props.name}</div>;
});
const Find = enhanceComponent(function Find(props) {
return <div>Find {props.name}</div>;
});
function enhanceComponent(OldComponent) {
class NewComponent extends React.PureComponent {
state = {
userInfo: {
name: "Tom",
age: 10,
},
};
render() {
return <OldComponent {...this.props} {...this.state.userInfo} />;
}
}
return NewComponent;
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>
高阶组件的应用场景二:真实应用场景 Context
我们在使用Context的时候,需要写类似下面的代码,这段代码看起来非常的繁琐,而且每次都需要这样写非常的恶心。通过高阶函数可以转变成即简单又优雅的实现方式。
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext();
class MyComponent extends React.PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={{ color: "red", size: 30 }}>
<Product />
</ThemeContext.Provider>
</div>
);
}
}
class Product extends React.PureComponent {
render() {
return (
<div>
<ThemeContext.Consumer>
{(value) => {
return <div>{value.size}</div>;
}}
</ThemeContext.Consumer>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
</script>
</body>