演示 : babel中文网试一试 let h1 =
ReactDOM.render( ,document.getElementById('root'))
渲染显示到页面[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hqEG5rw-1596329786917)(C:/Users/wangyu123/Desktop/新建文件夹/面试题/md-imgs/jsx.png)]
render () {
const el = <h1 className='greeting'>Hello JSX</h1>
console.log(el);
return el
}
react更新机制
简化 : 虚拟DOM
然后呢??
1. jsx => 虚拟DOM => 真实的DOM
2. jsx数据发生变化 => 新的虚拟DOM
3. 新的虚拟DOM 和 旧的虚拟DOM对比 => 通过Diff算法找到有差异的地方 => 更新1步骤的真实的DOM
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4SpZboe-1596329786920)(C:/Users/wangyu123/Desktop/新建文件夹/面试题/md-imgs/更新机制.png)]
//2. 类组件
class App extends React.Component {
state = {
}
render() {
return (
<div style={{ background:'pink', display:'flex' }}>
<P1></P1>
<P2></P2>
</div>
)
}
}
class P1 extends React.Component {
render() {
return (
<div style={{ background:'red',flex:1 }}>
<p>P1</p>
</div>
)
}
}
class P2 extends React.Component {
state = {
name :'zs'
}
render() {
return (
<div style={{ background: 'skyblue',flex:1 }}>
<p onClick={this.handle}>P2- { this.state.name }</p>
<C1></C1>
<C2></C2>
</div>
)
}
handle = () => {
this.setState({
name : 'ls'
})
}
}
class C1 extends React.Component {
render () {
console.log('child1 更新了');
return (
<div style={{background:'yellow' }}>
<p>child1</p>
</div>
)
}
}
class C2 extends React.Component {
render () {
console.log('child2 更新了');
return (
<div style={{background:'lime' }}>
<p>child2</p>
</div>
)
}
}
// 定时器的 timerId 就 不需要放到state 中,, 只是用来清除 定时器, 和渲染无关
componentDidMount() {
// timerId存储到this中,而不是state中
this.timerId = setInterval(() => {}, 2000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
// 演示1 : 子组件里 通过 shouldComponentUpdate() { return true or false }
- true-更新
- false-不更新
// 演示2 : 获取 父传过来的属性, 奇数更新,偶数不更新,
shouldComponentUpdate(nextProps,nextState)
- nextProps : 最新的属性
- nextState : 最新的状态
- if(nextState.count % 2 == 0) {
return false
}else {
return true
}
// 演示3 : 就一个组件里有number 值, 点击产生随机值, 比较前后生成的随机值是否一致,不一致就更细, 一致就不需要更新
// count : Math.floor(Math.random()*3)
shouldComponentUpdate(nextProps,nextState) {
// 上一个的值 最新的值
console.log(this.state.count === nextState.count)
// 本次的 nextState.number
// 上一次的 this.state.number
// 或者 通过 if..else..判断
return this.state.count !== nextState.count
}
class Hello extends React.PureComponent {}
// 把那个方法 shouldComponentUpdate() 删除掉, 已经 PureComponent已经封装好了
参考 : API Reference => React => React.PureComponent
PureComponent
说明 : PureComponent 内部会比较更新前后的 props 和 state 分别进行浅对比
对于简单/值类型来说, 比较两个值是否相同 (直接赋值即可, 没有坑)
// 将 React.Component 替换为: React.PureComponent
class Hello extends React.PureComponent {
state = {
// number 就是一个普通的值类型的数据
number: 0
}
handleClick = () => {
const newNumber = Math.floor(Math.random() * 3)
console.log(newNum);
// 对于 值类型 来说,没有任何坑,直接使用即可
// 更新前的 number:0
// 更新后的 number:2
// PureComponent 内部会进行如下对比:
// 更新前的number === 更新后的number
// 如果这两个值相同,内部,相当于在 shouldComponentUpdate 钩子函数中返回 false ,阻止组件更新。
// 如果这两个值不同,内部,相当于在 shouldComponentUpdate 钩子函数中返回 true ,更新组件。
this.setState({
number: newNumber
})
}
render() {
return (
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>随机生成</button>
</div>
)
}
}
const { obj } = this.state
let newNum = Math.floor(Math.random() * 3)
console.log(newNum);
// 虽然value 值 也可以从 react-dev-tools 里调试 value值确实都变了,但是 obj 一直没有变
obj.value = newNum
this.setState({
obj
})
const newObj = {...this.state.obj} // 创建一个新对象
newObj.value = Math.floor(Math.random() * 3)
this.setState({
obj: newObj
})
// 对象
state = {
obj : {
value : 123
}
}
//ES5
// 新加 之前的值 修改内容
const newObj = Object.assign( {}, this.state.obj, {value : '新的值'} )
this.setState({
obj:newObj
})
// es6
this.setState({
obj : {...this.state.obj, value: '新的值'}
})
// 数组:
state = {
list: ['a', 'b']
}
// ES5:
this.setState({
list: this.state.list.concat([ 'c' ]) // ['a', 'b', 'c']
})
// ES6:
this.setState({
list: [...this.state.list, 'c']
})
// 删除数组元素:[].filter()
(state, props) => UI
哈希值
与 展示视图
之间的一种对应关系spa缺点
1. 学习成本大 学习路由
2. 不利于SEO
yarn add react-router-dom
// 1 导入路由中的三个组件
import { BrowerRouter , Link, Route } from 'react-router-dom'
const Hello = () => {
// 2 使用 BrowerRouter 组件包裹整个应用(才能使用路由)
return (
<BrowerRouter>
<div>
<h1>React路由的基本使用:</h1>
{/* 3 使用 Link 组件,创建一个导航菜单(路由入口) */}
<Link to="/first">页面一</Link>
{/* 4 使用 Route 组件,配置路由规则以及要展示的组件(路由出口) */}
<Route path="/first" component={First} />
</div>
</BrowerRouter>
)
}
// HashRouter => 哈希模式 => 带 # => 修改的是 location.hash
import { HashRouter , Link, Route } from 'react-router-dom' //带#
// BrowserRouter => history模式 => 不带 # => 修改的是 location.pathname
import { BrowserRouter , Link, Route } from 'react-router-dom' //不带#
{/* Switch 只会让 组件显示出来一个 */}
<Switch>
<Route path="/one" component={One}></Route>
<Route path="/two" component={Three}></Route>
<Route path="/two" component={Two}></Route>
</Switch>
改变入口的三种方式 :
手动输入
声明式导航 : (html)
编程式导航 :
可以通过props 拿到 跳转和返回的方法
正常的组件, 打印 props => 默认是 一个空对象 {}
凡是参与路由匹配
出来的组件 , 路由都会给他们传入三个属性 history, location, match
history : (主要用来编程式导航)
location : (位置路径的)
pathname : 路径
match : 获取参数
* - push() 跳转到另外一个页面
* - goBack() 返回上一个页面
* - replace() 跳转到另外一个页面
*
* - push 和 replace 区别
* - push() 跳转 - 记录访问的历史 (可逆)
* - replace() 跳转 - 不记录访问的历史 (不可逆)
* One : <button onClick={this.jump}>跳转到two</button> 演示
* Two : <button onClick={this.back}>返回到One</button> 返回
HashRouter 传参的方式和 BrowserRouter 传参的方式不一样
this.props.history.push({
pathname: '/pay',
state: {
name: 'zs'
}
})
/
{/* / 表示默认路由规则 */}
<Route path="/" component={Home} />
问题:当 Link组件的 to 属性值为 “/login”时,为什么 默认路由
/
也被匹配成功?
// 添加 exact 之后, 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />
// 再演示 : /one/two/three
使用 重定向 '/' => '/one'
<Route exact path='/'
render={ () => {
return <Redirect to='/one' />
}
}></Route>
<Route exact path='/'>
<Redirect to='/one' />
</Route>
1. 访问路径 : http://localhost:8080/#/one
http://localhost:8080/#/two
2. 服务器接收到的 (服务器是不会 接收 # 后面的内容的)
3. 不管访问的路径是什么样的
http://localhost:8080 ==> 服务器返回的默认 的就是 index.html
4. 后面的 /one 和 /two 由路由来使用, 根据路由匹配规则找到对应的组件显示
5. 哈希模式 不管是 开发阶段还是发布阶段,都是没有问题的
1. 访问路径 : http://localhost:8080/one
http://localhost:8080/two
2. 服务器接收到的 http://localhost:8080/one 和 http://localhost:8080/two
但是,/one 和 /two 这个路径是不需要服务器端做任何处理的。
3. http://localhost:8080/getNews
http://localhost:8080/detail
它们都是 接口地址 , 后面遇到 类似 /one 和 /two 都会以为是接口 是要返回数据的呢?
3. 所以,应该在服务器端添加一个路由配置,直接返回 SPA 的 index.html 页面就行啦。
4. 类似处理
app.get('/getNews', (req,res) => {
// 根据 res 返回 对应的数据
res.json { ..... }
})
app.get('/detail', (req,res) => {
// 根据 res 返回 对应的数据
res.json { ..... }
})
// 最后 额外再多加一个, 专门用来返回 index.html
app.use('*', (req,res) => {
res.sendFile('index.html')
})
history模式 :
- 开发阶段 : webpack脚手架已经处理好了,
- 发布阶段 : 服务器是公司的服务器, 可能就会报错
- 我们要做的就是`告诉后台`,我们使用的 是 history模式,让他专门处理一下,就可以了
- 如果后台不给处理,或者处理不好, 我们就使用 `哈希模式`
类似 /one 和 /two 都会以为是接口 是要返回数据的呢?
3. 所以,应该在服务器端添加一个路由配置,直接返回 SPA 的 index.html 页面就行啦。
4. 类似处理
app.get(’/getNews’, (req,res) => {
// 根据 res 返回 对应的数据
res.json { … }
})
app.get(’/detail’, (req,res) => {
// 根据 res 返回 对应的数据
res.json { … }
})
// 最后 额外再多加一个, 专门用来返回 index.html
app.use('*', (req,res) => {
res.sendFile('index.html')
})
#### 总结 :
```js
history模式 :
- 开发阶段 : webpack脚手架已经处理好了,
- 发布阶段 : 服务器是公司的服务器, 可能就会报错
- 我们要做的就是`告诉后台`,我们使用的 是 history模式,让他专门处理一下,就可以了
- 如果后台不给处理,或者处理不好, 我们就使用 `哈希模式`