(1)类组件中this指向
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date().toLocaleTimeString(),
}
//5:创建一个新的绑定好this的函数绑定到this.go属性上
//5:只绑定一次,不必多次绑定解绑,消耗内存
this.go = this.go.bind(this)
}
render() {
return (
<div>
<h1>Clock</h1>
<h2>{this.state.time}</h2>
{/*1:点击时才会调用go函数*/}
<button onClick={this.go}>Go1</button>
{/*2:页面进行到此处时会直接执行go函数,虽然能把this指向实例但会重复调用render造成死循环*/}
{/*<button onClick={this.go()}>Go2</button>*/}
{/*3:箭头函数的this指向外围的render,render里的this又指向Clock,能够调用setState*/}
{/*但是箭头函数会重复声明,占用内存很大,性能不好,而且代码混乱,臃肿*/}
<button onClick={()=>{
this.setState({
time: new Date().toLocaleTimeString(),
})
}}>Go3</button>
{/*4:用bind改变this指向*/}
{/*每次渲染都重复绑定,重复生成新的函数*/}
<button onClick={this.go.bind(this)}>Go4</button>
{/*5:一次性绑定,当执行该步骤前this已经绑定好了,在constructor里绑定*/}
<button onClick={this.go}>Go5</button>
{/*6;ES7写法*/}
<button onClick={this.goES7}>Go6</button>
<button onClick={() => {
ReactDOM.unmountComponentAtNode(document.getElementById('app'))
}
}>remove</button>
</div>
)
}
go() {
//this指向undefined,不能调用setState
this.setState({
time: new Date().toLocaleTimeString()
})
}
//ES7新写法,解决this指向最优解
goES7=()=> {
this.setState({
time: new Date().toLocaleTimeString()
})
}
componentDidMount() {
//钩子函数中的this指向组件实例也就是Clock
// this.timerId = setInterval(() => {
// this.setState({
// time: new Date().toLocaleTimeString()
// })
// }, 1000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
}
ReactDOM.render(<Clock />, document.getElementById("app"))
钩子函数中的this指向组件实例,解决类组件中this指向问题的方法共有七种,最优解是ES7写法,其次是一次性绑定方法(上面代码中的第五种方法)
(2)setState同步与异步问题
class App extends React.Component{
state = {a:0,b:0,c:0}
handleClick=()=>{
//1:直接修改数据,无法触发render,不能重新渲染
this.state.a = 1
//2:setState修改数据,在合成事件中是异步的
this.setState({a:1})
console.log(this.state.a);
//4:多次使用setState都是异步,不是遇到一个立即执行,会合并,只执行一次render,批量更新
//批量更新的时机在render之前
this.setState({a:1})
this.setState({b:1})
this.setState({c:1})
console.log(this.state);
//5:累加也是异步的,不是立即执行
//当调用时,不能依赖上一个状态来计算下一个状态
this.setState({a:1})
this.setState({a:this.state.a+1})
this.setState({a:this.state.a+1})
//解决方案
perState是上次的状态值
this.setState((perState)=>{
return {a:perState.a+1}
})
//源码中setState(obj,callback),其中obj可以是函数也可以是对象
//callback就是一个回调函数,所以可以用callback取值
//6:通过callback来取出异步结果
this.setState((perState,props)=>({a:perState.a+1}),()=>{
console.log('callback',this.state.a);
})
//7:定时器中setState是同步的,同步事件不会合并,不会有依赖问题
setTimeout(()=>{
this.setState({
a:1
})
console.log(this.state.a);
},1000)
}
render(){
console.log('render');
return (
<div>
<h1>a:{this.state.a}</h1>
<h1>b:{this.state.b}</h1>
<h1>c:{this.state.c}</h1>
{/*合成事件(事件代理,委托)*/}
<button onClick={this.handleClick}>button1</button>
</div>
)
}
componentDidMount(){
console.log('componentDidMount');
//3:钩子函数里也是异步的
this.setState({
a:1
})
console.log(this.state.a);
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
在合成事件中,除setState在setTimeout和setInterval中是同步的,其他情况下都是异步的;钩子函数中也是异步的;但是在原生事件中,setState是同步的
(3)解决异步方案
function sum(a,b){
setTimeout(()=>{
return a+b
},2000)
}
var a =sum(1,2)
console.log(a)//undefined
//1:回调函数:给函数传一个函数并在指定的时机去执行
function sum(a,b,callback){
let result = a+b
setTimeout(()=>{
callback(result)
},2000)
}
function callback(result){
console.log(result);
}
sum(1,2,callback)//打印出3
var a = sum(1,2,callback)
console.log(a);//undefined
//2:promise对象解决
function sum(a, b) {
return new Promise((resolve, reject) => {
let result = a + b
setTimeout(() => {
resolve(result)
}, 2000)
})
}
//then实际上就是实现result,catch实际上是reject
sum(1, 2).then((result) => {
console.log(result);
}).catch(() => {
console.log(err);
})
利用promise解决setState问题
class App extends React.Component {
state = { a: 0, b: 0, c: 0 }
handleClick = () => {
var result = this.setState({ a: 1 })
console.log(result); //undefined
this.setState({ a: 1 }, () => {
console.log('callback1', this.state.a);
})
}
//await在async里,所以必须再此添加async
handleClickAsync = async () => {
//通过then的方式来取值
this.setStatePromise({
a:1
})
this.setStatePromise(10)
this.setStatePromise((preState,props)=>{
return {a:preState.a + 2}
})
.then(()=>{
console.log('promise获取值:',this.state.a);
})
.catch(err=>{
console.log(err);
})
//3:通过async await解决
await this.setStatePromise({
a:1
})
//捕获错误后不报红
try{
await this.setStatePromise(111)
console.log(this.state.a);
}
catch(error){
console.log("捕获",error);
}
}
//封装方法
setStatePromise = (newState) => {
//函数返回值是一个promise对象
return new Promise((resolve, reject) => {
if (typeof newState === 'object' || typeof newState === 'function') {
//调用setState
this.setState(newState, resolve())
} else {
reject('setStatePromise 参数只能是对象或者函数')
}
})
}
render() {
return (
<div>
<h2>a:{this.state.a}</h2>
<h2>b:{this.state.b}</h2>
<h2>c:{this.state.c}</h2>
<button onClick={this.handleClick}>button</button>
<button onClick={this.handleClickAsync}>buttonAsync</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
await函数会同步代码等待异步代码先执行,这样就能拿到值了
(4)简易登陆和退出切换
function Welcome(props) {
return <h1>Welcome</h1>
}
function Login(props) {
return <h1>Please Login</h1>
}
function Greeting(props) {
const isLogin = props.isLogin
console.log("Greeting触发");
if (isLogin) {
return <Welcome />
}
else {
return <Login />
}
}
function LoginButton(props) {
console.log('函数组件登陆按钮触发');
return <button onClick={props.login}>Login</button>
}
function LogoutButton(props) {
console.log('函数组件退出按钮触发');
return <button onClick={props.logout}>Logout</button>
}
class LoginControl extends React.Component {
state = { isLogin: true }
handleLogin = () => {
console.log('handleLogin');
this.setState({
isLogin: true
})
}
handleLogout = () => {
console.log('handleLogout');
this.setState({
isLogin: false
})
}
render() {
//在此处获取状态,在判断时使用
const isLogin = this.state.isLogin
return (
<div>
<Greeting isLogin={this.state.isLogin} />
{
//此处的状态为上文const变量获取的状态,登陆状态下显示logout,退出状态下显示login
isLogin ? <LogoutButton logout={this.handleLogout} /> : <LoginButton login={this.handleLogin} />
}
</div>
)
}
}
let vDOM = <LoginControl />
ReactDOM.render(vDOM, document.getElementById('app'))
判断是否登陆内部处理顺序为:已经登陆,显示退出按钮,将在LoginControl组件中定义的事件传播给函数组件LogoutButton,渲染出LogoutButton;当退出按钮被点击时,触发事件,改变state的值,变为退出状态,render重新渲染,显示登陆按钮,将LoginControl组件中定义的事件传播给函数组件LoginButton;点击登陆按钮时有进行循环
(5)map和key
// map:取出数组每一项进行逻辑处理然后返回一个新的数组
// key要加在离数据源最近的地方,在兄弟节点之间具有唯一性,全局下可以不唯一
let source = [1, 2, 3, 4, 5]
let double = source.map(item => item * 2)
function ListItem(props) {
let { value } = props
return (
<li>{value * 2}</li>
)
}
function ListContainer(props) {
let { data } = props
//通过value把值传到ListItem
let listItems = data.map(item => <ListItem key={item} value={item} />)
return (
<ul>
{listItems}
</ul>
)
}
ReactDOM.render(<ListContainer data={source} />, document.getElementById('app'))
(6)标题与内容练习
function Content(props) {
let { title, content } = props
return (
<div>
<h3>{title}</h3>
<h3>{content}</h3>
</div>
)
}
function Title(props) {
let { title } = props
return (
<li>{title}</li>
)
}
function Blogs(props) {
let { posts } = props
let sidebar = (
<ul>
{
//此处对数据已经处理完了,所以存到变量里,直接使用就行了
posts.map(item => <Title key={item.id} title={item.title} />)
}
</ul>
)
let content = posts.map(item => <Content key={item.id} title={item.title} content={item.content} />)
//变量是数组,自动遍历了
return (
<div>
<div>{sidebar}</div>
<div>{content}</div>
</div>
)
}
const posts = [
{ id: 1, title: 'title1', content: 'welcome to harbin' },
{ id: 2, title: 'title2', content: 'welcome to mdj' },
]
ReactDOM.render(<Blogs posts={posts} />, document.getElementById('app'))