React(二):JSX语法解析+综合案例
事件绑定
this绑定方式
问题:在事件执行后,需获取当前类的对象中相关属性,此时需要this——当打印时,发现this为undefined,这又是为啥?
假设有一个btnClick函数,但它并不是我们主动调用的,而是当button发生改变时,React内部调用→内部调用时,并不知道要如何绑定正确的this
解决办法:
- bind给btnClick显示绑定this
- 使用 ES6 class fields 语法
- 事件监听时传入箭头函数(较为推荐)
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/babel.js"></script>
<script type="text/babel">
/*
this的四种绑定规则
1.默认绑定:独立执行foo()
2.隐式绑定:被一个对象执行obj.foo() ->obj
3.显示绑定:call/apply/bind foo.call('aaa') ->String('aaa')
4.new绑定:new Foo() -> 创建一个新对象,并赋值给this
*/
//1.创建root
const root = ReactDOM.createRoot(document.getElementById('root'))
//2..定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
count:100
}
}
btn1Click() {
this.setState({
count:this.state.count+1
})
}
// 箭头函数无this,只能去上一层找
btn2Click = () => {
this.setState({
count:this.state.count-1
})
}
btn3Click() {
this.setState({
count:this.state.count-1
})
}
render(){
const {count} = this.state
return (
<div>
<h1>{ count }</h1>
{/* 1.this绑定方式一:bind绑定 */}
<button onClick={this.btn1Click.bind(this)}>按钮1</button>
{/* 2.this绑定方式二:箭头函数ES6 class fields */}
<button onClick={this.btn2Click}>按钮2</button>
{/* 3.this绑定方式三:直接传入一个箭头函数 */}
<button onClick={() => this.btn3Click()}>按钮3</button>
</div>
)
}
}
// 3.将App组件渲染到root上
root.render(<App />)
</script>
</body>
事件参数传递
1.获取默认参数:即event对象
2.获取更多参数:可通过传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/babel.js"></script>
<script type="text/babel">
//1.创建root
const root = ReactDOM.createRoot(document.getElementById('root'))
//2..定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
message:"Hello World"
}
}
btnClick (event,name,age) {
console.log("点击事件", event,this);
console.log("name:", name, "age:",age);
}
render(){
const {message} = this.state
return (
<div>
{/* 1.event参数的传递 */}
<button onClick={this.btnClick}>按钮1</button>
<button onClick={(event) => this.btnClick(event)}>按钮2</button>
{/* 2.传递额外的参数 */}
<button onClick={(event) => this.btnClick(event, "why", 30)}>按钮3</button>
</div>
)
}
}
// 3.将App组件渲染到root上
root.render(<App />)
</script>
</body>
条件渲染
在vue中,我们会通过指令来控制:比如v-if、v-show;
在React中,所有的条件判断都和普通的JavaScript代码一致;
常见条件渲染方式:
1.条件判断语句
2.三元运算符
3.与运算符&&
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/babel.js"></script>
<script type="text/babel">
//1.创建root
const root = ReactDOM.createRoot(document.getElementById('root'))
//2..定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
isReady:true,
friend:{
name:"lucy",
age:22,
gender:"女"
}
}
}
render(){
const {isReady,friend} = this.state
// 1.条件判断方式一:使用if进行条件判断
let showElement = null
if(isReady) {
showElement = <h2>准备开始比赛吧</h2>
}else{
showElement = <h1>我还没有准备好嘞</h1>
}
return (
<div>
{/* 方式一:根据条件给变量赋值不同的内容 */}
<div>{ showElement }</div>
{/* 方式二:三元运算符 */}
<div>{isReady? <button>准备开始</button> : <button>还没有准备好</button> }</div>
{/* 方式三:&&运算符 */}
{/* 场景:当某一个值,有可能为undefined时,使用&&进行条件判断 */}
<div>{ friend && <div>{friend.name + friend.age + friend.gender}</div> }</div>
</div>
)
}
}
// 3.将App组件渲染到root上
root.render(<App />)
</script>
</body>
实现v-show的效果:
class App extends React.Component {
constructor() {
super()
this.state = {
message:"Hello World",
isShow:true
}
}
ediClick() {
this.setState({
isShow:!this.state.isShow
})
}
render(){
const {message,isShow} = this.state
return (
<div>
<button onClick={this.ediClick.bind(this)}>切换1</button>
<h1 >{ isShow? message : "" }</h1>
{/* v-show的效果 */}
<button onClick={this.ediClick.bind(this)}>切换2</button>
<h1 style={{display:isShow ? "block" : "none"}}>{ message }</h1>
</div>
)
}
}
列表渲染
class App extends React.Component {
constructor() {
super()
this.state = {
students:[
{id:1, name:'Jack', age:18},
{id:2, name:'Tom', age:20},
{id:3, name:'Lucy', age:22},
{id:4, name:'Lily', age:24}
]
}
}
render(){
const {students} = this.state
// 展示年龄大于20的
const filterStudents = students.filter(item => item.age >= 20)
return (
<div>
<h2>学生信息列表</h2>
<div className="list">
{/* 绑定唯一标识key:提高diff算法时的效率 */}
{filterStudents.map(item =>
<div key={item.id} className="item">
<p>id:{item.id}</p>
<p>姓名:{item.name}</p>
<p>年龄:{item.age}</p>
</div>
)}
</div>
</div>
)
}
}
原理本质
babel转换
实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖→所有的jsx最终都会被转换成React.createElement的函数调用
createElement需要传递三个参数:
1.参数一:type
- 当前ReactElement的类型;
- 如果是标签元素,那么就使用字符串表示 “div”;
- 如果是组件元素,那么就直接使用组件的名称;
2.参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的形式存储;
- 比如传入className作为元素的class;
3.参数三:children
- 存放在标签中的内容,以children数组的方式进行存储;
直接编写jsx代码:
<div>
<div className="header">header</div>
<div className="Content">
<div>Banner</div>
<ul>
<li>数据列表1</li>
<li>数据列表2</li>
<li>数据列表3</li>
</ul>
</div>
<div className="footer">footer</div>
</div>
经过babel转译后:
React.createElement(
'div',
null,
React.createElement('div', { className: 'header' }, 'header'),
React.createElement(
'div',
{ className: 'Content' },
React.createElement('div', null, 'Banner'),
React.createElement(
'ul',
null,
React.createElement('li', null, '数据列表1'),
React.createElement('li', null, '数据列表2'),
React.createElement('li', null, '数据列表3')
)
),
React.createElement('div', { className: 'footer' }, 'footer')
);
虚拟DOM生成
通过React.createElement最终创建出一个ReactElement对象→它组成了一个JS对象树→即虚拟DOM
阶段案例-购物车
<body>
<div id="root"></div>
<script src="../lib/react.js"></script>
<script src="../lib/react-dom.js"></script>
<script src="../lib/babel.js"></script>
<script src="./data.js"></script>
<script src="./format.js"></script>
<script type="text/babel">
// 1.定义App根组件
class App extends React.Component {
constructor() {
super()
this.state = {
books: books
}
}
getTotalPrice() {
const totalPrice = this.state.books.reduce((preValue, item) => {
return preValue + item.count * item.price
}, 0)
return totalPrice
}
changeCount(index, count) {
const newBooks = [...this.state.books]
newBooks[index].count += count
this.setState({ books: newBooks })
}
removeItem(index) {
const newBooks = [...this.state.books]
newBooks.splice(index, 1)
this.setState({ books: newBooks })
}
renderBookList() {
const { books } = this.state
return <div>
<table>
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item, index) => {
return (
<tr key={item.id}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{formatPrice(item.price)}</td>
<td>
<button
disabled={item.count <= 1}
onClick={() => this.changeCount(index, -1)}
>
-
</button>
{item.count}
<button onClick={() => this.changeCount(index, 1)}>+</button>
</td>
<td><button onClick={() => this.removeItem(index)}>删除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h2>总价格: {formatPrice(this.getTotalPrice())}</h2>
</div>
}
renderBookEmpty() {
return <div><h2>购物车为空, 请添加书籍~</h2></div>
}
render() {
const { books } = this.state
return books.length ? this.renderBookList(): this.renderBookEmpty()
}
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
</script>
</body>
format.js(数值格式化文件)
function formatPrice(price) {
return "¥" + Number(price).toFixed(2)
}
data.js(数据文件)
const books = [
{
id: 1,
name: '《算法导论》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《UNIX编程艺术》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《编程珠玑》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《代码大全》',
date: '2006-3',
price: 128.00,
count: 1
},
]