赞
踩
首先安装Node:保姆级别教程
在git bash中输入: npm install create-react-app -g
,然后输入create-react-app --version
,如果能正常显示版本号,那么安装就成功了。
目录下右键 => git bash => create-react-app 项目名
=> 回车等几分钟就欧了。
注意项目名不能包含大写字母。
脚手架目录下 => npm run start
,然后就可以看到非常帅气的大花。
没啥用的先删了,我们自己搭建src中的文件:
好,那么接下来我们重新写一下src里面的文件:
index.js
//重写react代码,并且通过react渲染出来对应的内容
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App/>);
App.jsx import React from 'react'; import HelloReact from './Components/HelloReact'; class App extends React.Component { constructor() { super(); this.state = { name:'zzy' } } render() { return ( <div> <h2>奥里给</h2> <button>按钮</button> <HelloReact/> </div> ) } } export default App;
HelloReact.jsx import React from 'react'; class HelloReact extends React.Component { constructor() { super(); this.state = { name: 'react' } } render() { return ( <div> <h2>Hello React!</h2> </div> ) } } export default HelloReact
类组件的定义有如下要求:
React.Component
render
函数使用class定义一个组件:
this.state
中维护的就是我们组件内部的数据;render()
方法是 class 组件中唯一必须实现的方法;class App extends React.Component { constructor() { super(); this.state = { name:'zzy' } } render() { return [ <div> <h2>奥里给</h2> <button>按钮</button> <HelloReact/> </div>, <div></div> ] } }
render函数的返回值可以是什么?
<div />
会被 React 渲染为 DOM 节点,<MyComponent />
会被 React 渲染为自定义组件;<div />
还是 <MyComponent />
均为 React 元素(通过creatElement创建出来的东西)。函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。
函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):
this
(组件实例);state
);我们来定义一个函数组件:
//函数式组件
function App() {
//返回的东西和render返回的是一样的。
return <h1>我是一个函数组件</h1>
}
export default App;
生命周期的概念和vue中是一样的,只不过在React中钩子更少一些。
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;
我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(后面我们可以通过hooks来模拟一些生命周期的回调)
这张图画的还是非常不错的,在这里插入代码片
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
constructor中通常只做两件事情:
1、通过给 this.state
赋值对象来初始化内部的state;
2、为事件绑定实例(this);
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。
componentDidMount中通常进行哪里操作呢?
依赖于DOM的操作可以在这里进行;
在此处发送网络请求就最好的地方;(官方建议)
可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
componentDidUpdate()
会在更新后会被立即调用,首次渲染不会执行此方法。
当组件更新后,可以在此处对 DOM 进行操作;
如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
componentWillUnmount()
会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作;
例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等;
我们创建累组件App,并将HelloReact组件作为它的子组件:
App组件: class App extends React.Component { constructor() { console.log('APP-constructor') super(); this.state = { name: 'zzy' } } changeData() { this.setState({ name: 'ht' }) } render() { console.log('App-render'); const { name } = this.state; return ( <div> <h2>{name}</h2> <button onClick={() => this.changeData()}>点击修改数据</button> {name == 'zzy' && <HelloReact />} </div> ) } componentDidMount() { console.log('App-componentDidMount') } componentDidUpdate() { console.log('App-componentDidUpdate') } }
HelloReact组件 class HelloReact extends React.Component { constructor() { console.log('HR-constructor') super(); this.state = { name: 'react' } } render() { console.log('HR-render') return ( <div> <h2>Hello React!</h2> </div> ) } componentDidMount() { console.log('HR-componentDidMount') } componentDidUpdate() { console.log('HR-componentDidUpdate') } componentWillUnmount() { console.log('HR-componentWillUnmount') } }
让我们看一下控制台的输出:
不难看出生命周期的一个顺序:
对于挂载来说:父组件constuctor
=> 父组件render
=> 子组件constructor
=> 子组件render
=> 子组件挂载完毕 => 父组件挂载完毕
对于更新来说,如果要让子组件从页面上消失,那么点击跟新执行父组件render函数后子组件会走销毁的钩子,然后走子组件更新完毕的钩子,和图是一样滴。
请参考:React官方文档生命周期
在了解React中的组件通信前,我们先搭建一个组件嵌套结构:
class App extends React.Component {
render() {
const { name } = this.state;
return (
<div>
<Header/>
<Main/>
<Footer/>
</div>
)
}
}
Footer和Header长得一样
class Header extends React.Component {
render() {
return (
<div>
<h2>Header</h2>
</div>
)
}
}
class Main extends Component {
render() {
return (
<div>
<h2>Main</h2>
<Banner/>
<ProductList/>
</div>
)
}
}
class Banner extends Component {
render() {
return (
<div>Banner</div>
)
}
}
class ProductList extends Component {
render() {
return (
<div>ProductList</div>
)
}
}
这里我们主要研究Main
给Banner或ProductList
组件传数据,我们可以准备一些静态数据。
和vue一样,在子组件标签处写属性名,传变量用{}
,传其他用字符串。
class Main extends Component { constructor() { super(); this.state = { banner: ['动作射击', '角色扮演', '策略运营'], productList: ['古墓丽影','镜之边缘','神秘海域','奥里给'] } } render() { const { banner, productList } = this.state; return ( <div> <h2>Main</h2> <Banner banner={banner} /> <ProductList title="商品列表" productList={productList}/> </div> ) } }
然后子组件在constructor
中使用super(props)
继承父类的props
属性,并把props
这个属性设置为传进来的值。
当然啊,如果不写constructor,也能自动接收到props,这是一个小细节。
class ProductList extends Component { constructor(props) { console.log(props); super(props); } render() { console.log(this.props) const { title, productList } = this.props; return ( <div> <div>{title}</div> <ul> {productList.map(item => { return <li key={item}>{item}</li> })} </ul> </div> ) } }
接下来我们就可以队商品列表和标题进行展示:
我们以Main
组件和ProductList
组件为例,将Main中的数据传给ProductList
class Main extends Component { constructor() { super(); this.state = { productList: ['古墓丽影','镜之边缘','神秘海域','奥里给'] } } render() { const { banner, productList } = this.state; return ( <div> <h2>Main</h2> <ProductList age={18} title="商品列表" productList={productList}/> <ProductList age={10}/> </div> ) } }
接下来我们就可以在ProductList
组件中对接收的数据进行类型和默认值的设置:
import React, { Component } from 'react'; import PropTypes from 'prop-types'; class ProductList extends Component { constructor(props) { console.log(props); super(props); } render() { console.log(this.props); const { age,title, productList } = this.props; return ( <div> <h3>ProductList</h3> <div>{age}</div> <div>{title}</div> <ul> {productList.map(item => { return <li key={item}>{item}</li> })} </ul> </div> ) } }
//props接收数据类型的限定
ProductList.propTypes = {
title: PropTypes.string,
age: PropTypes.number.isRequired, //必须是数字类型,必须有
productList: PropTypes.array,
}
//props接收数据的默认值
ProductList.defaultProps = {
title: '我的网页的干活',
productList: [],
}
如上,首先引入PropTypes
import PropTypes from 'prop-types';
然后在类组件名字后面设置属性propTypes
。
默认值则设置属性defaultProps
。
其他细节可以翻看官方文档:props默认值大全
有些新东西比如我们现在可以通过static关键字直接在组件中写:
class Greeting extends React.Component {
static defaultProps = {
name: 'zzy'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
这里的方法和vue中类似(vue还可以用组件自定义事件)
1、在父组件中通过属性给子组件传递一个回调
class App extends React.Component {
getData(data) {
console.log(data);
}
render() {
return (
<div>
<Son getSonData={(data) => this.getData(data)}/>
</div>
)
}
}
2、子组件可以调用父组件传的函数,并把组件数据作为参数传递过去,然后父组件就能拿到子组件的数据并进行后续操作。
class Son extends Component { constructor(props) { super(props); this.state = { sonData: '我是子组件数据' } } sendData() { //调用父组件传的函数并把子组件数据作为参数传过去 this.props.getSonData(this.state.sonData); } render() { return ( <div> <h2>子组件</h2> <button onClick={this.sendData.bind(this)}>点击把子组件数据传给父组件</button> </div> ) } }
给我实现下面这个效果:
这里我们把上面的导航栏封装成组件Navigate
,整体的思路如下:
1、父组件存放数据,先传给子组件一份
2、子组件接收数据并遍历展示
3、子组件添加按钮,动态显示类名active(原理就是通过点击事件修改currentIndex)
4、父组件给子组件一个回调,子组件动态显示类名后,把当前index传给父组件
5、父组件接收index并存起来,然后在下面展示对应的数据。
class App extends React.Component { constructor() { super(); this.state = { navList: ['新款', '精选', '流行'], contentIndex: 0, } } getContent(index) { this.setState({ contentIndex: index }) } render() { let { navList, index } = this.state; return ( <div> <Navigation navList={navList} getContent={(index) => this.getContent(index)} /> <h2>{navList[contentIndex]}</h2> </div> ) } }
export class Navigate extends Component { constructor(props) { super(props); this.state = { currentIndex: 0, } } changeIndex(index) { this.setState({ currentIndex: index }) this.props.getContent(index); } render() { let { currentIndex } = this.state; let { navList } = this.props return ( <div className='nav'> {navList.map((nav, index) => { return ( <div key={nav} className={`title ${currentIndex === index ? 'active' : ''}`} onClick={() => this.changeIndex(index)} > {nav} </div> ) })} </div> ) } }
备注:react中使用scss:npm add node-sass@npm:dart-sass
// 安装scss:npm add node-sass@npm:dart-sass
.nav {
border: 2px solid black;
display: flex;
justify-content: space-around;
.title {
padding: 10px;
&.active {
color: red;
border-bottom: 3px solid red;
}
}
}
插槽也是父子通信的一种方式
我们在父组件中的子组件标签内部写几个div:
父组件App
render() {
return (
<div>
<Navigation>
<div className="left">左边</div>
<div className="middle">中间</div>
<div className="right">右边</div>
</Navigation>
</div>
)
}
那么子组件中就可以通过this.props.children
读取到我们写的这些div,如果写多个,那么children是一个数组
,如果写一个,那么children就是一个react元素
(当然啊,我们可以通过propType限制children的类型是数组还是react元素)。
子组件
export class Navigation extends Component {
render() {
//props中的children可以接收到子组件插槽中的react元素
let {children} = this.props;
return (
<div className='box'>{children}</div>
)
}
}
这样的话,我们就可以拿着这些东西去子组件展示
上面这种用children接收的方式有个问题,就是接到父组件的react元素默认是按照子组件书写顺序传入children数组的,这样通过索引去写可能会有展示的顺序问题。,而且比较麻烦
render() {
//props中的children可以接收到子组件插槽中的react元素
let {children} = this.props;
console.log(children)
return (
<div className='box'>
<div>{children[0]}</div>
<div>{children[1]}</div>
<div>{children[2]}</div>
</div>
)
}
比第一种更好的方式,就是我们在父组件中的子组件标签上直接添加属性,传入相应的react元素,子组件就可以通过props直接读取,直接用,非常奈斯
父组件
render() {
const left = <div className="left">左边</div>;
const middle = <div className="middle">中间</div>;
const right = <div className="right">右边</div>;
return (
<div>
{/* 2.第二种方式:直接通过props传react元素 */}
<Navigation left={left} middle={middle} right={right}/>
</div>
)
}
子组件可以根据属性名随意切换顺序,不用去通过索引找元素
子组件
render() {
//props中的children可以接收到子组件插槽中的react元素
let {children,left,middle,right} = this.props;
console.log(children)
return (
<div className='box'>
{left}
{right}
{middle}
</div>
)
}
本质上还是父给子传个函数,然后子去调用并把当前的数据传给父组件,父组件根据数据的类型,返回不同的节点,这里就不写了。
使用{...props}
这种react官方提供的解构语法,可以直接把数据传下去,举个例子:
父组件 export class App extends Component { constructor() { super(); this.state = { person: { name: 'zzy', age: 18 } } } render() { let { person } = this.state; return ( <div> {/* 1.第一种传递方式,繁琐 */} <Son name={person.name} age={person.age}/> {/* 2.第二种传递方式:直接解构 */} <Son {...person}/> </div> ) } }
儿子
export class Son extends Component {
render() {
return (
<div>
{/* 2.传过来的props也是一个对象,直接结构继续往下传 */}
<GrandSon {...this.props} />
</div>
)
}
}
孙子
export class GrandSon extends Component {
render() {
return (
<div>
<GGrandSon {...this.props}/>
</div>
)
}
}
曾孙可以直接拿到数据直接用
export class GGrandSon extends Component {
render() {
let {name, age} = this.props;
return (
<div>
<h2>我拿到了数据</h2>
<div>{name}-{age}</div>
</div>
)
}
}
使用Context可以直接把数据给任意一层组件:
使用步骤:
1、使用React.createContext()
定义一个context
2、父组件引入一下
import myContext from './context'
在子组件外边包一个标签(也可以在孙子组件包,可以理解为给谁传就给谁包,如果儿子组件,那么孙子组件也可以用步骤3的方法获取数据,曾孙也可以),名字是刚才定义的名字.Provider
,然后加上value
属性,属性值就是要传的值。
{/* 3.第三种传递方式:context */}
<myContext.Provider value={{name:'ht', age:'10'}}>
<Son/>
</myContext.Provider>
3、子组件或孙子组件通过添加contextType
属性为可以把数据添加到this.context
上(说的官方点,就是订阅我们这个myContext):
import React, { Component } from 'react'; import GrandSon from './GrandSon'; import myContext from '../context' export class Son extends Component { render() { console.log('Son',this.context); return ( <div> {this.context.name} <GrandSon /> </div> ) } } Son.contextType = myContext; //3.添加contextType
对于函数组件,我们需要这样来做:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。