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() ->obj3.显示绑定:call/apply/bind foo.call('aaa') ->String('aaa')4.new绑定:new Foo() -> 创建一个新对象,并赋值给this*///1.创建rootconst 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.statereturn (<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.创建rootconst 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.statereturn (<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.创建rootconst 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 = nullif(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.statereturn (<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 += countthis.setState({ books: newBooks })}removeItem(index) {const newBooks = [...this.state.books]newBooks.splice(index, 1)this.setState({ books: newBooks })}renderBookList() {const { books } = this.statereturn <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.statereturn 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},
]