ReactJS语法-React 组件
函数组件
- 在React中,构造函数,就是一个最基本的组件
- 如果想要把组件放到页面中,可以把 构造函数的名称,当作 组件的名称,以 HTML标签形式引入页面中即可
- 注意: React在解析所有的标签的时候,是以标签的首字母来区分的,如果标签的首字母是小写,那么就按照 普通的 HTML 标签来解析,如果 首字母是大写,则按照 组件的形式去解析渲染,即组件的首字母必须是大写
- 特点:
- 没有生命周期函数
- this关键字不能指向组件实例
- 没有内部状态(state)
//1. 定义组件Hello,首字母必须大写
function Hello() {
return <div>
<h1>这是在Hello组件中定义的元素</h1>
</div>
}
//2. 渲染组件
ReactDOM.render(<div>
<Hello></Hello>
</div>,document.getElementById('app'))
组件传值
//1. 定义组件Hello,首字母必须大写
function Hello(props) {
// 在组件中,如果想要使用外部传递过来的数据,必须显示的在 构造函数参数列表中,定义 props 属性来接收;
// 通过 props 得到的任何数据都是只读的,不能重新赋值
// props.name = '000'
return <div>
<h1>这是在Hello组件中定义的元素---{props.name}</h1>
</div>
}
// var name = 'zs'
// var age = 20
var person = {
name: 'ls',
age: 22,
gender: '男',
address: '北京'
}
//2. 渲染组件
ReactDOM.render(<div>
{//<Hello name={person.name} age={person.age} gender={person.gender} address={person.address}></Hello>}
{//简要写法}
<Hello {...person}></Hello>
</div>,document.getElementById('app'))
// 注意:这里 ...Obj 语法,是 ES6中的属性扩散, 表示把这个对象上的所有属性,展开了,放到这个位置
jsx文件
-
将上面封装的Hello组件,单独放到一个Hello.jsx文件中
import React from 'react' function Hello(props) { return <div> <h1>这是在Hello组件中定义的元素 --- {props.name}</h1> <p id="">哈哈哈哈</p> </div> } // 把创建的组件暴露出去 export default Hello -
main.js中引用
import Hello from './components/Hello.jsx'
class创建组件
- 组件的首字母必须是大写
- 使用 class 创建的类,通过 extends 关键字,继承React.Component,这个类,就是一个组件的模板
- 在 class 实现的组件内部,必须定义一个 render 函数
- 在 render 函数中,返回的是JSX内容,如果没有什么需要被return 的,则需要 return null。
- 如果想要引用这个组件,可以把类的名称以标签形式,导入到JSX中使用
-
创建一个Hello2组件
import React from 'react' class Hello2 extends React.Component { // 必须定义render函数 render() { // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null return <div> <h1>这是 使用 class 类创建的组件</h1> </div> } }组件中的数据
- 在组件中的数据,可以分成两类:
- 参与界面更新的数据:当数据变化时,需要更新组件染的内容,
- 不参与界面更新的数据:当数据变化时,不需要更新将组建渲染的内容;
- 参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中
- 可以通过在构造函数中 this.state ={定义的数据}
- 当我们的数据发生变化时,可以调用 this.setState 来更新数据,并且通知React进行update操作;
- 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
- 注意:this.setState方法是异步的。并不能在执行完setState之后立马拿到最新的state的结果
组件数据传值
class Hello2 extends React.Component {
// 在 constructor 中,如果想要访问 props 属性,不能直接使用 this.props, 而是需要在 constructor 的构造器参数列表中,显示的定义 props 参数来接收,才能正常使用;
constructor(props) {
// 注意: 如果使用 extends 实现了继承,那么在 constructor 的第一行,一定要显示调用一下 super(),super() 表示父类的构造函数
super(props)
console.log(props)
// 注意: 这是固定写法,this.state 表示 当前组件实例的私有数据对象
// 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
this.state = {
msg: '这是 Hello2 组件的私有msg数据',
info: '瓦塔西***'
}
}
render() {
// 虽然在 React dev tools 中,并没有显示说 class 组件中的 props 是只读的,但是,经过测试得知,其实 只要是 组件的 props,都是只读的;
// this.props.address = '123'
return <div>
<h1>这是 使用 class 类创建的组件</h1>
<h3>外界传递过来的数据是: {this.props.address} --- {this.props.info}</h3>
<h5>{this.state.msg}</h5>
</div>
}
}
ReactDOM.render(<div>
<Hello2 address="xxx" info="XXX程序员"></Hello2>
</div>, document.getElementById('app'))
子组件给父组件传递数据
- 在React中是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可。
//父组件给子组件传递一个函数,用来接收子组件的数据,并渲染页面
export class App extends Component {
constructor(){
super()
this.state ={
counter: 180
}
}
changecounter(count){
this.setstate({ counter:this.state.counter + count})
}
render(){
const{ counter }= this.state
return(
<div>
<h2>当前计数:{counter}</h2>
<Addcounter addclick={(count)=> this.changecounter(count)}/>
</div>
)
}
}
//子组件点击事件变更数据,调用父组件传递过来的函数,将参数传递给父组件
export class Addcounter extendsComponent {
addcount(count) {
console.log("count:",count)
this.props.addclick(count)
}
render(){
return(
<div>
<button onclick={e => this.addcount(1)}>+1</button>
</div>
)
}
}
访问组件的私有数据
class Hello2 extends React.Component {
constructor(props) {
super(props)
this.state = {
msg: '这是 Hello2 组件的私有msg数据',
info: '瓦塔西***'
}
}
render() {
return <div>
<h1>这是 使用 class 类创建的组件</h1>
<h3>外界传递过来的数据是: {this.props.address} --- {this.props.info}</h3>
//访问私有数据
<h5>{this.state.msg}</h5>
//修改组件私有数据
{/* 1.1 在React中,如果想要为元素绑定事件,不能使用 网页中 传统的 onclick 事件,而是需要 使用 React 提供的 onClick */}
{/* 1.2 也就是说:React中,提供的事件绑定机制,使用的 都是驼峰命名,同时,基本上传统的 JS 事件,都被 React 重新定义了一下,改成了 驼峰命名 onMouseMove */}
{/* 2.1 在 React 提供的事件绑定机制中,事件的处理函数,必须直接给定一个 function,而不是给定一个 function 的名称 */}
{/* 2.2 在为 React 事件绑定 处理函数的时候,需要通过 this.函数名, 来把 函数的引用交给 事件 */}
<input type="button" value="修改 msg" id="btnChangeMsg" onClick={this.changeMsg} />
<br />
</div>
}
changeMsg = () => {
// 直接使用 this.state.msg = '123' 为 state 上的数据重新赋值,可以修改 state 中的数据值,但是,页面不会被更新;
// 所以这种方式,React 不推荐,以后尽量少用;
// this.state.msg = '123'
// 如果要为 this.state 上的数据重新赋值,那么,React 推荐使用 this.setState({配置对象}) 来重新为 state 赋值,一旦数据发生改变页面会自动刷新!!!!!
// 注意: this.setState 方法,只会重新覆盖那些 显示定义的属性值,如果没有提供最全的属性,则没有提供的属性值,不会被覆盖(info属性);
/* this.setState({
msg: '123'
}) */
// this.setState 方法,也支持传递一个 function,如果传递的是 function,则在 function 内部,必须return 一个 对象;
// 在 function 的参数中,支持传递两个参数,其中,第一个参数是 prevState,表示为修改之前的 老的 state 数据
// 第二个参数,是 外界传递给当前组件的 props 数据
this.setState(function (prevState, props) {
// console.log(props)
return {
msg: '123'
}
}, function () {
//callback:回调函数
// 由于 this.setState 是异步执行的,所以,如果想要立即拿到最新的修改结果,最保险的方式, 在回调函数中去操作最新的数据
console.log(this.state.msg)
})
// 经过测试发现, this.setState 在调用的时候,内部是异步执行的,所以,当立即调用完 this.setState 后,输出 state 值可能是旧的
// console.log(this.state.msg)
}
}
Hello2单独创建jsx组件
-
将以上的Hello2组件单独封装到Hello2.jsx如下:
import React from 'react' export default class Hello2 extends React.Component { //....不变 } -
main.js导入
// 在使用 Hello 组件之前,先导入 组件 import Hello2 from './components/Hello2.jsx'
两种创建组件方式比较
- 使用 function 构造函数创建的组件,内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据;
- 使用 class 关键字 创建的组件,内部,除了有 this.props 这个只读属性之外,还有一个 专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的!
- 使用 function 创建的组件,叫做无状态组件;使用 class 创建的组件,叫做有状态组件
- 有状态组件和无状态组件,最本质的区别,就是有无 state 属性;同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数;
- 什么时候使用 有状态组件,什么时候使用无状态组件呢?
- 如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件;
- 如果一个组件,只需要根据外界传递过来的 props,渲染固定的 页面结构就完事儿了,此时,非常适合使用 function 创建出来的 无状态组件;(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一些)
props&state
- props属性只读,不能修改
- state为组件私有数据属性,一旦通过
this.setState({配置对象})设置,自动刷新页面.
React组件的插槽
- 在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素。
- React对于这种需要插槽的情况非常灵活,有两种方案可以实现:
- 组件的children子元素;
- props属性传递React元素;
children子元素实现插槽
export class App extends Component {
render(){
return(
<div>
{/*给导航栏组件传递子元素*/}
<NavBar>
<button>按钮</button>
<h2>我是标题</h2>
<i>斜体文字</i>
</NavBar>
</div>
)
}
}
//通用导航栏组件
export class NavBar extends Component {
render(){
//获取到NavBar组件的子元素
//注意!!!:如果外部传递给NavBar的子元素只有一个,那么这个children就不是素组了,而是这个唯一的子元素
const {children }= this.props
return(
<div className='nav-bar'>
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
)
}
}
props属性实现插槽
export class App extends Component {
render(){
return(
<div>
{/*给导航栏组件传递props*/}
<NavBarTwo
leftslot={<button>按钮2</button>}
centerslot={<h2>呵呵呵</h2>}
rightslot={<i>斜体2</i>}
/>
</div>
)
}
}
//通用导航栏组件
export class NavBar extends Component {
render(){
const {leftslot, centerslot, rightslot} = this.props
return(
<div className='nav-bar'>
<div className="left">{leftslot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightslot}</div>
</div>
)
}
}
ref获取组件和组件中的DOM
组件中如何获取DOM
- 在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作,官方不建议用js原生方法获取DOM。
-
可以通过设置refs来获取DOM,有三种方式:
import React,{ Purecomponent, createRef}from'react' export class App extends Purecomponent{ constructor() { super() this.titleRef= createRef() this.titleel = nul1 } getNativeDoM(){ //1.方式一:在React元素上绑定一个ref字符串 //console.log(this.refs.coderzhong) //2.方式二:提前创建好ref对象,createRef(),将创建出来的对象绑定到元素,最常用该方法 //console.log(this.titleRef.current) //3.方式三:传入一个回调函数,在对应的元素被渲染之后,回调函数被执行,并且将元素传入 console.log(this.titleEl) } render(){ return( <div> <h2 ref="coderzhong">Hello World</h2> <h2 ref={this.titleRef}>你好啊,xxx</h2> <h2 ref={el =>{ this.titleEl = el }}>你好啊</h2> <button onclick={e => this.getNativeDoM()]>获取DOM</button> </div> } } export default App
ref获取组件
-
获取类组件实例
export class App extends Purecomponent{ constructor(){ super() this.hwRef = createRef() } getcomponent(){ //获取Helloworld组件 console.log(this.hwRef.current) //调用Helloworld组件内部的实例方法 this.hwRef.current.test(); } render(){ return( <div> <Helloworld ref={this.hwRef}/> <button onclick={e =>this.getcomponent()}>获取组件实例</button> </div> ) } } -
无法获取函数式组件,但是可以获取函数式组件中的dom(略)
受控组件、非受控组件
- 在 HTML 中,表单元素(如
<input>、<textarea>和<select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。即:比如input用键盘输入内容,浏览器会自动显示到输入框内。—-非受控组件 - 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新.即react刷新页面必须通过setState。—–受控组件
- 这个“受控”指的是:被React控制,用setState来渲染界面。
- 那么如何将“非受控组件”转变为受控组件呢?
- 给非受控组件绑定value值。
- 监听非受控组件的输入操作onchange。
- 使用setState来渲染非受控组价
- 注意: 以上3个条件缺一不可,如果只有1,那么input则不可以输入文本。
-
举例:
constructor(){ super() //给input赋默认值 this.state = {username : "123"} } inputchange(event){ console.log("inputchange:",event.target.value) //3. 使用setState来渲染非受控组价 this.setstate({ username:event.target.value }) } render(){ const { username}=this.state return( <div> {/*受控组件:1. 给非受控组件绑定value值。2. 监听非受控组件的输入操作onchange*/} <input type="text" value={username} onchange={le=>this.inputchange(e)}/> </div> ) }
高阶组件
- 高阶函数:接收一个或者多个函数做为输入,或者输出一个函数。
- 高阶组件:Higher-Order Components,简称为 HOC,高阶组件是参数为组件返回值为新组件的函数;
- 高阶组件 本身不是一个组件,而是一个函数;
- 这个函数的参数是一个组件,返回值也是一个组件,
使用场景
- 可以对组件进行过滤封装
-
模拟登录界面:登录就展示购物车,未登录就展示请登录,把这个是否登录的判断放到组件中
//App.jsx import React, { PureComponent } from 'react' import Cart from './pages/Cart' export class App extends PureComponent { constructor() { super() } loginClick() { //模拟登录 localStorage.setItem("token", "coderzh") // 强制更新 this.forceUpdate() } render() { return ( <div> App <button onClick={e => this.loginClick()}>登录</button> <Cart/> </div> ) } } export default App //高阶组件,用于判断 function loginAuth(OriginComponent) { return props => { // 从localStorage中获取token const token = localStorage.getItem("token") if (token) { return <OriginComponent {...props}/> } else { return <h2>请先登录, 再进行跳转到对应的页面中</h2> } } } export default loginAuth //购物车组件 import React, { PureComponent } from 'react' import loginAuth from '../hoc/login_auth' export class Cart extends PureComponent { render() { return ( <h2>Cart Page</h2> ) } } //调用高阶组件函数 export default loginAuth(Cart)
fragment
- 在之前的开发中,总是在一个组件中返回内容时包裹一个div元素:
- 也可以不渲染这样一个div:使用Fragment,Fragment 允许将子列表分组,而无需向 DOM 添加额外节点;
-
React还提供了Fragment的短语法:它看起来像空标签
<> </>export class App extends Purecomponent { render(){ return ( {/**Fragment不会被渲染/} <Fragment> <h2>我是App的标题</h2> </Fragment> ) } } export default App
组件的CSS样式导入
-
实现场景:实现一个评论列表.,创建CommentList.jsx
import React from 'react' // 导入当前组件需要的子组件 import CommentItem from './CommentItem.jsx' // 评论列表组件 export default class CommentList extends React.Component { constructor(props) { super(props) // 定义当前评论列表组件的 私有数据 this.state = { cmts: [ { user: '张三', content: '哈哈,沙发' }, { user: '张三2', content: '哈哈,板凳' }, { user: '张三3', content: '哈哈,凉席' }, { user: '张三4', content: '哈哈,砖头' }, { user: '张三5', content: '哈哈,楼下山炮' } ] } } // 在 有状态组件中,render 函数是必须的,表示,渲染哪些 虚拟DOM元素并展示出来 render() { //返回每一行item return <div> {/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */} {this.state.cmts.map((item, i) => { return <div key={i}> <h1>评论人:{item.user}</h1> <h1>评论内容:{item.content}</h1> </div> })} </div> } } -
main.js
// JS打包入口文件 // 1. 导入 React包 import React from 'react' import ReactDOM from 'react-dom' // 导入评论列表组件 import CommentList from './components/comment1/CommentList.jsx' ReactDOM.render(<div> <CommentList></CommentList> </div>, document.getElementById('app')) - 封装评论项:CommentItem
- 把列表的每一行封装成一个item
- 新建CommentItem.jsx
import React from 'react' // 封装一个 评论项 组件,此组件由于不需要自己的 私有数据,所以直接定义为 无状态组件 export default function CommentItem(props) { return <div> <h1>评论人:{props.user}</h1> <h1>评论内容:{props.content}</h1> </div> } -
CommentList.jsx中返回每一行item修改
return <div> <h1 className="title">评论列表案例</h1> {/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */} {this.state.cmts.map((item, i) => { // return <CommentItem user={item.user} content={item.content} key={i}></CommentItem> return <CommentItem {...item} key={i}></CommentItem> })} </div>
style内联样式:给CommentItem添加样式
-
如何添加样式?CommentItem.jsx文件
// 注意: 如果要使用 style 属性,为 JSX 语法创建的DOM元素,设置样式,不能像网页中那么写样式;而是要使用JS语法来写样式 // 在 写 style 样式的时候,外层的 { } 表示 要写JS代码了,内层的 { } 表示 用一个JS对象表示样式 // 注意: 在 style 的样式规则中,如果 属性值的单位是 px, 则 px 可以省略,直接写一个 数值 即可 export default function CommentItem(props) { return <div style=> <h1 style=>评论人:{props.user}</h1> <h1 style=>评论内容:{props.content}</h1> </div> } -
优化
export default function CommentItem(props) { //单独封装成对象 const boxStyle = { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 } const titleStyle = { fontSize: 16, color: "purple" } const bodyStyle = { fontSize: 14, color: "red" } //直接使用对象 return <div style={boxStyle}> <h1 style={titleStyle}>评论人:{props.user}</h1> <h1 style={bodyStyle}>评论内容:{props.content}</h1> </div> } -
优化2
//把 样式对象,封装到唯一的一个对象中 const inlineStyles = { boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 }, titleStyle: { fontSize: 16, color: "purple" }, bodyStyle: { fontSize: 14, color: "red" } } return <div style={inlineStyles.boxStyle}> <h1 style={inlineStyles.titleStyle}>评论人:{props.user}</h1> <h3 style={inlineStyles.bodyStyle}>评论内容:{props.content}</h3> </div> -
优化3:
-
将样式独立封装到cmtItemStyle.js文件中去
// 导入一个 样式的对象 export default { boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 }, titleStyle: { fontSize: 16, color: "purple" }, bodyStyle: { fontSize: 14, color: "red" } } -
CommentItem.js文件导入cmtItemStyle.js
import inlineStyles from './cmtItemStyles.js'
-
CSS外联样式导入使用
优化4:CommentItem.js中不采用行内样式style方式
-
新建CommentItem.css
/* 注意:当启用 CSS 模块化之后,这里所有的类名,都是私有的,如果想要把类名设置成全局的一个类,可以把这个类名,用 :global() 给包裹起来 */ /* 当使用 :global() 设置了全局的 类样式之后,这个类不会被重命名 */ //私有 .box{ border: 1px solid #ccc; padding-left: 15px; box-shadow: 0 0 6px #ccc; margin: 10px 0; } //公有,通过普通方式导入当前CSS文件的都可以使用,比如CommentList.jsx通过import CommentItem from './CommentItem.jsx',导入,只能使用:global配置的css属性 /* 只有私有的类才会被重命名 */ :global(.title){ color:red; text-align: center; } //私有:只有通过模块化导入当前CSS文件的才能被使用 .title{ color: green; font-size: 16px; } //私有 .body{ font-size: 14px; color:red; } -
模块化CSS:导入CSS时能够限制导入的CSS只能用于当前文件,避免css串用
- 在CommentItem.jsx导入CommentItem.css
// 导入评论项的样式文件【这种直接 import '../路径标识符' 的 CSS 导入形式,并不是模块化的CSS】 // import '../../css/commentItem.css' // 默认情况下,如果没有为 CSS 启用模块化,则接收到的 itemStyles 是个空对象,因为 .css 样式表中,不能直接通过 JS 的 export defualt 导出对象 // 当启用 CSS 模块化之后(在webpack.config.js中配置启用CSS模块化,modules:true),导入 样式表得到的 itemStyles 就变成了一个 样式对象,其中,属性名是 在样式表中定义的类名,属性值,是自动生成的一个复杂的类名(防止类名冲突) import itemStyles from '../../css/commentItem.css' console.log(itemStyles) ... return <div className={itemStyles.box}> <h1 className={itemStyles.title}>评论人:{props.user}</h1> <h3 className={itemStyles.body}>评论内容:{props.content}</h3> </div> ...
css modules
- css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
- 如果在其他项目中使用它,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
- React的脚手架已经内置了css modules的配置
- 使用方式
- 将XXX.css/.less/.scss 等样式文件都需要修改成 XXX.module.css/.module.less/.module.scss
- 引入:
import itemStyles from '../../css/commentItem.module.css'
-
举例使用:
//APP.module.css文件 .title { font-size: 32px; color: green; } .content { font-size: 22px; color: orange; } .hy-title { } //App.jsx import React, { PureComponent } from 'react' import appStyle from "./App.module.css" export class App extends PureComponent { render() { return ( <div> <h2 className={appStyle.title}>我是标题</h2> <p className={appStyle.content}>我是内容, 哈哈哈哈</p> </div> ) } } export default App
绑定this并传参的三种方式
- 如果render中的标签点击事件绑定的是普通函数,那么普通函数中的this指向是undefind.
- 解决方案:
- 使用bind
- 使用箭头函数:把当前函数变成箭头函数
- 使用call/apply
方式一:bind绑定传递
- bind 的作用:修改函数内部的this指向,让函数内部的this,指向bind 参数列表中的 第一个参数
- bind 和 call/apply 之间的区别:call/apply 修改完this指向后,会立即调用前面的函数,但是 bind 只是修改this指向,并不会调用。
- 注意: bind 中的第一个参数,是用来修改 this 指向的,第一个参数后面的所有参数,都会当作将来调用 前面函数 时候的参数传递进去
-
创建BindThis.jsx组件
import React from 'react' export default class BindThis extends React.Component { constructor(props) { super(props) this.state = { msg: '这是默认的msg' } } //render中的标签点击事件绑定的是普通函数 render() { return <div> <h1>绑定This并传参的几种方式</h1> {/* 方式1:在 事件处理函数中,直接使用 bind 绑定 this 并传参 */} <input type="button" value="绑定this并传参的方式1" onClick={this.changeMsg1.bind(this, '🐷', '🍕')} /> <hr /> <h3>{this.state.msg}</h3> </div> } /** * 普通实例函数 * 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined */ changeMsg1(arg1, arg2,event) { //event,默认传递 // console.log(this); this.setState({ msg: '绑定this并传参的方式1' + arg1 + arg2 }) } } -
main.js导入
import BindThis from './components/BindThis.jsx' //render中渲染 ReactDOM.render(<div> <BindThis></BindThis> </div>, document.getElementById('app'))
方式二:在构造函数中bind绑定并传参
- 当一个函数,调用 bind 改变了this指向后,bind 函数调用的结果返回值,就是被改变this指向后的函数的引用;
-
实现普通函数changeMsg2
changeMsg2(arg1, arg2) { // console.log(this); this.setState({ msg: '绑定this并传参的方式2' + arg1 + arg2 }) } -
render中点击第二个按钮
<input type="button" value="绑定this并传参的方式2" onClick={this.changeMsg2} /> -
constructor绑定this并传参
constructor(props) { super(props) this.state = { msg: '这是默认的msg' } // 绑定 this 并传参的方式2: 在构造函数中绑定并传参 // 注意,当一个函数,调用 bind 改变了this指向后,bind 函数调用的结果返回值,就是被改变this指向后的函数的引用; // 注意: bind 不会修改 原函数的 this 指向 this.changeMsg2 = this.changeMsg2.bind(this, '🚗', '👫') }
方式三:使用箭头函数绑定this并传参
-
实现箭头函数changeMsg3
changeMsg3 = (event,arg1, arg2) => { // console.log(this); this.setState({ msg: '绑定this并传参的方式3' + arg1 + arg2 }) } -
render中点击第三个按钮
<input type="button" value="绑定this并传参的方式3" onClick={(event) => { this.changeMsg3(event,'😊', '😘') }} />
评论列表案例

-
创建评论列表组件CmtList.jsx
import React from 'react' import CMTItem from './CmtItem.jsx' import CMTBox from './CmtBox.jsx' // 评论列表组件 export default class CMTList extends React.Component { constructor(props) { super(props) this.state = { list: [ { user: 'zs', content: '123' }, { user: 'ls', content: 'qqq' }, { user: 'xiaohong', content: 'www' } ] } } // 在组件渲染完毕获取数据 componentDidMount() { this.loadCmts() } render() { return <div> <h1>这是评论列表组件</h1> {/* 发表评论的组件 */} {/* react 中,只要是传递给 子组件的数据,不管是 普通的类型,还是方法,都可以使用 this.props 来调用 */} <CMTBox reload={this.loadCmts}></CMTBox> <hr /> {/* 循环渲染一些评论内容组件 */} {this.state.list.map((item, i) => { return <CMTItem key={i} {...item}></CMTItem> })} </div> } // 从本地存储中加载 评论列表 loadCmts = () => { var list = JSON.parse(localStorage.getItem('cmts') || '[]') this.setState({ list }) } } -
创建评论item组件CmtItem.jsx
import React from 'react' // 评论列表项组件 export default class CMTItem extends React.Component { render() { return <div style=> <h3>评论人:{this.props.user}</h3> <h5>评论内容:{this.props.content}</h5> </div> } } -
创建评论组件CmtBox.jsx
import React from 'react' // 评论列表框组件 export default class CMTBox extends React.Component { render() { return <div> <label>评论人:</label><br /> <input type="text" ref="user" /><br /> <label>评论内容:</label><br /> <textarea cols="30" rows="4" ref="content"></textarea><br /> <input type="button" value="发表评论" onClick={this.postComment} /> </div> } postComment = () => { // 1. 获取到评论人和评论内容 // 2. 从 本地存储中,先获取之前的评论数组 // 3. 把 最新的这条评论,unshift 进去 // 4. 在把最新的评论数组,保存到 本地存储中 var cmtInfo = { user: this.refs.user.value, content: this.refs.content.value } var list = JSON.parse(localStorage.getItem('cmts') || '[]') list.unshift(cmtInfo) localStorage.setItem('cmts', JSON.stringify(list)) this.refs.user.value = this.refs.content.value = '' this.props.reload() } } -
main.js中引用
import React from 'react' import ReactDOM from 'react-dom' import CmtList from './components/Comment/CmtList.jsx' // 使用 render 函数渲染 虚拟DOM ReactDOM.render(<div> <BindThis></BindThis> </div>, document.getElementById('app'))
context特性
- 如果一个组件1嵌套组件2嵌套组件3,组件1为父组件,2为子组件,3为孙组件。
-
如果组件1要给组件3传递数据,通常的方法是通过props属性层层传递
export default class Com1 extends React.Component { constructor(props) { super(props) this.state = { color: 'red' } } render() { return <div> <h1>这是 父组件 </h1> <Com2 color={this.state.color}></Com2> </div> } } // 中间的子组件 class Com2 extends React.Component { render() { return <div> <h3>这是 子组件 </h3> <Com3 color={this.props.color}></Com3> </div> } } // 内部的孙子组件 class Com3 extends React.Component { render() { return <div> <h5 style=>这是 孙子组件 </h5> </div> } } -
通过context直接传递
// 最外层的父组件 export default class Com1 extends React.Component { constructor(props) { super(props) this.state = { color: 'red' } } // getChildContextTypes // 1. 在 父组件中,定义一个 function,这个function 有个固定的名称,叫做 getChildContext ,内部,必须 返回一个 对象,这个对象,就是要共享给 所有子孙自建的 数据 getChildContext() { return { color: this.state.color } } // 2. 使用 属性校验,规定一下传递给子组件的 数据类型, 需要定义 一个 静态的(static) childContextTypes(固定名称,不要改) static childContextTypes = { color: ReactTypes.string // 规定了 传递给子组件的 数据类型 } render() { return <div> <h1>这是 父组件 </h1> <Com2></Com2> </div> } } // 中间的子组件 class Com2 extends React.Component { render() { return <div> <h3>这是 子组件 </h3> <Com3></Com3> </div> } } // 内部的孙子组件 class Com3 extends React.Component { // 3. 上来之后,先来个属性校验,去校验一下父组件传递过来的 参数类型 static contextTypes = { color: ReactTypes.string // 这里,如果子组件,想要使用 父组件通过 context 共享的数据,那么在使用之前,一定要先 做一下数据类型校验 } render() { return <div> <h5 style=>这是 孙子组件 --- {this.context.color} </h5> </div> } }
React过渡动画实现
- 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。
- 当然,可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group用来完成过渡动画。
-
这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装:
//npm安装 npm install react-transition-group --save //yarn安装 yarn add react-transition-group
react-transition-group主要组件
- Transition:该组件是一个和平台无关的组件(不一定要结合CSS)
- CSSTransition: 在前端开发中,通常使用CSSTransition来完成过渡动画效果
- SwitchTransition: 两个组件显示和隐藏切换时,使用该组件
- TransitionGroup: 将多个动画组件包裹在其中,一般用于列表中元素的动画
CSSTransitio
- CSSTransition是基于Transition组件构建
- CSSTransition执行过程中,有三个状态:appear、enter、exit;
- 它们有三种状态,需要定义对应的CSS样式
- 第一类,开始状态:对于的类是-appear、-enter、exit;
- 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;
- 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;
- CSSTransition常见对应的属性:
- in:触发进入或者退出状态
- 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉;
- 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
- 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
- classNames:动画class的名称
- 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
- timeout:过渡动画的时间
- appear:是否在初次进入添加动画(需要和in同时为true)
- unmountOnExit:退出后卸载组件
- in:触发进入或者退出状态
- CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作
- onEnter:在进入动画之前被触发;
- onEntering:在应用进入动画时被触发;
- onEntered:在应用进入动画结束后被触发
import React, { createRef, PureComponent } from 'react'
import { CSSTransition } from "react-transition-group"
import "./style.css"
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
isShow: true
}
this.sectionRef = createRef()
}
render() {
const { isShow } = this.state
return (
<div>
<button onClick={e => this.setState({isShow: !isShow})}>切换</button>
{/* 实现过度动画*/}
<CSSTransition
nodeRef={this.sectionRef}
in={isShow}
unmountOnExit={true}
classNames="why"
timeout={2000}
appear
onEnter={e => console.log("开始进入动画")}
onEntering={e => console.log("执行进入动画")}
onEntered={e => console.log("执行进入结束")}
onExit={e => console.log("开始离开动画")}
onExiting={e => console.log("执行离开动画")}
onExited={e => console.log("执行离开结束")}
>
<div className='section' ref={this.sectionRef}>
<h2>哈哈哈</h2>
<p>我是内容, 哈哈哈</p>
</div>
</CSSTransition>
</div>
)
}
}
export default App