目录结构
index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import App from './components/app';
//index.js是整js的入口
//将定义好的组件渲染出来
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
box.js
import React, { Component } from 'react';
class Box extends Component {
//如果想在外面修改当前state的值,就不能在当前组件里面存储state
//就要在当前组件渲染外面state的值
//当前这个组件里面所有用到state的地方,都需要修改成我们传过来的值
// state = {
// //把值传过来之后,可以将这个值更新到state里面
// x:this.props.x,//他就可以将x初始化进来
// // x:0,
// }
//将当前组件里的state删掉,如果要删掉的话,handleClickLeft和handleClickRight也会修改自己state的值
//删完之后我们就不能修改自己state的值了,所以这两个函数也需要移到外面
//将这两个函数剪切在boxes里
// handleClickLeft=(step)=>{
// this.setState({
// // 只会在初始化的时候执行一次
// x:this.state.x-step
// })
// console.log("click left",this);
// }
// handleClickRight(step){
// this.setState({
// x:this.state.x+step
// })
// console.log("click right",this);
// }
//1Mount周期
// constructor(){
// super();
// console.log("Box - Constructor");
// }
// //3Mount周期
// componentDidMount(){
// console.log("Box - Mounted");
// }
//2 Update周期
// componentDidUpdate() {
// console.log("Box - Updated");
// }
//Unmount周期
//每一个组件可能会绑定一些事件,我们可以在删除之前做一个清理,把一些事件、数据清理掉
componentWillUnmount(){
console.log("Box - Unmout");
}
//将所有用到state的地方都变成this.props.box
render() {
//2Mount周期
// console.log("Box - Rendered");
// 1.4.2 从上往下传递数据
//props里存储的就是在这个组件在Boxes中定义的时候里面填写的各种属性,除了key以外
// console.log(this.props);
return (
<React.Fragment>
{/* 1.4.3 传递子节点 */}
{/* {this.props.children[0]} */}
<div style={this.getStyles()}>{this.toString()}</div>
{/* {this.props.children[1]} */}
<button onClick={this.props.onClickLeft} className='btn btn-primary m-2'>left</button>
<button onClick={this.props.onClickRight} className='btn btn-success m-2'>right</button>
{/* 在box里面调用boxes的函数 */}
<button onClick={()=>this.props.onDelete(this.props.box.id)} className='btn btn-danger m-2'>Delete</button>
</React.Fragment>
);
}
getStyles(){
let styles = {
width: 50,
height:50,
backgroundColor: "lightblue",
color:"white",
textAlign:"center",
lineHeight:"50px",
borderRadius:"5px",
marginLeft: this.props.box.x,
};
if(this.props.box.x<=0) styles.backgroundColor='orange';
return styles;
}
toString() {
const {x}=this.props.box;
return `x:${x}`;
}
}
export default Box;
boxes.jsx
import React, { Component } from 'react';
import Box from './box';
//在维护state值的时候,同一个数据只能存一份,如果存多份的时候就会出现有些时候我们改完之后会发现,有些地方的值变了,但是有些地方的值不变,就会发生数据不同步的情况
//把boxes变成一个函数组件sfc
//函数组件相当于是只有函数组件
//对象的解构可以在参数里面做,解构完之后就可以将props删掉了
// const Boxes = ({onReset,boxes,onDelete,onClickLeft,onClickRight}) => {
// return (
// <React.Fragment>
// <button
// onClick={onReset}
// style={{marginBottom:"15px"}} className='btn btn-dark'>Reset</button>
// {boxes.map(box=>(
// <Box
// key={box.id}
// box={box}
// onDelete={onDelete}
// onClickLeft={()=>onClickLeft(box)}
// onClickRight={()=>onClickRight(box)}
// />
// ))}
// </React.Fragment>
// );
// }
// export default Boxes;
class Boxes extends Component {
//将state的所有内容放到app里面去,将定义和操作他的所有内容都放过去
// state = {
// // 把所有Box的所有信息存到state里面
// //boxes存一个数组,boxes里面每一个需要存一个x,另外需要存一个id,因为react组件如果有若干个儿子的话,儿子的key需要不一样
// //删完之后直接在box组件里面直接用boxes组件里面的state值就可以了
// boxes: [
// {id:1,x:1},
// {id:2,x:2},
// {id:3,x:3},
// {id:4,x:4},
// ]
// }
// //实现在当前组件里改完x之后,能够影响到box里面的值:
// // 可以将box里的state值直接删掉,由于box里面的state的值不能在外面修改,所以直接将他弃用(删掉)就可以了
// handleClickLeft=(box)=>{
// //注意: 不要直接修改this.state的值,因为setState函数可能会将修改覆盖掉。
// //所以先将boxes复制一遍
// const boxes=[...this.state.boxes];
// //indexOf可以在一个数组里面找到某一个书的下标
// const k=boxes.indexOf((box));
// //将第k个boxes的值重新克隆出来,因为boxes[k]他其实存的是原数组里的第k个值
// //把他克隆出来可以再展开一遍,再展开一遍就相当于是把它创建了一个新的
// boxes[k]={...boxes[k]};
// boxes[k].x--;//将新的这个值得x减减
// this.setState({boxes});//将对于boxes的修改更新到state里面
// }
// handleClickRight(box){
// const boxes=[...this.state.boxes];
// const k=boxes.indexOf((box));
// boxes[k]={...boxes[k]};
// boxes[k].x++;
// this.setState({boxes});
// }
// handleReset = () => {
// const boxes=this.state.boxes.map(b=>{
// // 点完reset之后,当前这个组件boxes里面的state里面的值确实变成0了
// // 但是并没有真的将dom里面的值变成0
// // 因为如果向修改某一个组件值,一定只能在组件内部修改,不能在另外一个组件里修改这个组件的值
// // (即,不能在boxes里修改box的x值),修改的只是当前组件boxes里的state,
// // 但是最后在div中显示出来的x值其实应该是box组件里的state
// // box里面的state x:this.props.x,这句话他只会在第一次执行的时候将box里面的state的值改变成从boxes传过来的值
// // 但是后面每次修改的时候x:this.props.x这句话不会重复执行,他只会在初始化的时候执行一次
// // 所以后面不管怎么去修改x:this.props.x里面的值,x都不会发生变化了
// return{
// id:b.id,
// x:0,
// }
// });
// this.setState({boxes});
// console.log(this.state)
// }
// handleDelete = (boxId) => {
// console.log("handle delete", boxId)
// const boxes=this.state.boxes.filter(b=>b.id!==boxId);
// //语法糖:在一个对象里如果key和value是一样的话,可以只写一个
// // this.setState({ boxes: boxes });
// this.setState({ boxes});
// }
//1Mount周期
// constructor(){
// super();
// console.log("Boxes - Constructor");
// }
// //3Mount周期
// componentDidMount(){
// console.log("Boxes - Mounted");
// }
//2 Update周期
// componentDidUpdate() {
// console.log("Boxes - Updated");
// }
render() {
//2Mount周期
// console.log("Boxes - Rendered");
return (
// 当一个组件里面包含多个并列的元素的时候,我们需要用一个标签将他们括起来
// 可以用react里的一个虚拟标签将他们括起来<React.Fragment>
<React.Fragment>
<button
onClick={this.props.onReset}
style={{marginBottom:"15px"}} className='btn btn-dark'>Reset</button>
{/* 改成用一个map来写
<Box />
<Box />
<Box />
<Box /> */}
{this.props.boxes.map(box=>(
//可以将所有要传的数据的参数写在<Box />里面,可以任意起名
//例如向传一个x,写完x之后,就可以在Box组件里面找到这个属性了
<Box
key={box.id}
// x={box.x}
//如果只定义了名称没定义值的话,值就是true
// name='xxx'
//也可以传一个对象 box={box}
// id={box.id}
box={box}
// 将函数传给box里面的onClick
onDelete={this.props.onDelete}
//为了能够在box组件内部调用组件外部的函数,需要将组件外部的函数作为属性传过去
onClickLeft={()=>this.props.onClickLeft(box)}
onClickRight={()=>this.props.onClickRight(box)}
/>
// {/* 如果在定义的组件之间加一些子元素的话,子元素的值也是可以传过来的 */}
// <h1>Box:</h1>
// <p>#{box.id}</p>
// </Box>
))}
</React.Fragment>
);
}
}
export default Boxes;
navbar.jsx
import React, { Component } from 'react';
// 函数组件sfc
//函数组件相当于是只有一个render函数,把render函数的内容复制过来,然后将其余内容删掉
//类组件是可以通过this.props传递信息的,但是函数组件props是传在参数里面,要将所有的this都删掉
// const NavBar = (props) => {
// return (
// <nav className="navbar bg-body-tertiary">
// <div className="container-fluid">
// <a className="navbar-brand" href="/">
// Navbar <span>Boxes Count: {props.boxesCount}</span>
// </a>
// </div>
// </nav>
// );
// }
// export default NavBar;
class NavBar extends Component {
// 类组件里如果没有局部变量的话,就可以把他变成一个无状态的函数组件
state = { }
//1Mount周期
// constructor(){
// super();
// console.log("NavBar - Constructor");
// }
// //3Mount周期
// componentDidMount(){
// console.log("NavBar - Mounted");
// }
//2 Update周期
// componentDidUpdate() {
// console.log("NavBar - Updated");
// }
render() {
//2Mount周期
// console.log("NavBar - Rendered");
return (
<nav className="navbar bg-body-tertiary">
<div className="container-fluid">
{/* /表示根目录 */}
<a className="navbar-brand" href="/">
Navbar <span>Boxes Count: {this.props.boxesCount}</span>
</a>
</div>
</nav>
);
}
}
export default NavBar;
// 在navbar里面存储一些boxes的信息,例如存储一下boxes里面有多少个元素,存一个count,涉及到不同组件之间传递信息,兄弟组件之间传递信息
// 在navar里调用boxes里的信息,只能把这两个节点要调用的信息放到他们的最近公共祖先App里,所以只能将boxes的state放到app里面
app.jsx
import React, { Component } from 'react';
import NavBar from './navbar';
import Boxes from './boxes';
//有两个组件navbar和boxes
class App extends Component {
state = {
boxes: [
{id:1,x:1},
{id:2,x:2},
{id:3,x:3},
{id:4,x:4},
]
}
// Mount周期
//3.componentDidMount是说整个对象都已经被渲染出来了
//如果想从服务器加载一些数据渲染出来,需要先将整个组件挂载完之后,再往里填数据
//所以挂载完之后可以写一个ajax方法,从服务器加载数据
//从服务器加载完之后,就可以通过setState函数将我们从数据库加载的信息渲染到我们当前组件里
// componentDidMount(){
// // ajax();
// // this.setState();
// console.log('App - Mounted');
// }
// //构造函数,由于它是从某一个类里面继承过来的,那么在构造函数里第一句话一定要写super,先初始化构造函数
// //1.先创建,Mount周期
// constructor(){
// super();
// console.log("App - Constructor");
// }
//2 Update周期
//prevProps和prevState存储我们上一个属性(即修改之前)和上一个状态
// componentDidUpdate(prevProps,prevState) {
// console.log("App - Updated");
// // console.log("prevProps",prevProps);
// //输出上一个state的值和更新完之后的值
// console.log("prevState",prevState,this.state);
// //当发现状态不一样的时候,可能会更新数据库里的数据
// //所以一般往数据库里写数据的时候,很多时候都会写componentDidUpdate函数里面
// // if(prevState.boxes[0].x!==this.state.boxes[0].x){
// // ajax()//更新数据库
// // }
// }
handleClickLeft=(box)=>{
const boxes=[...this.state.boxes];
const k=boxes.indexOf((box));
boxes[k]={...boxes[k]};
boxes[k].x--;
this.setState({boxes});
}
handleClickRight=(box)=>{
const boxes=[...this.state.boxes];
const k=boxes.indexOf((box));
boxes[k]={...boxes[k]};
boxes[k].x++;
this.setState({boxes});
}
// handleClickRight(box){
// const boxes=[...this.state.boxes];
// const k=boxes.indexOf((box));
// boxes[k]={...boxes[k]};
// boxes[k].x++;
// this.setState({boxes});
// // console.log("click right")
// }
handleReset = () => {
const boxes=this.state.boxes.map(b=>{
return{
id:b.id,
x:0,
}
});
this.setState({boxes});
}
handleDelete = (boxId) => {
console.log("handle delete", boxId)
const boxes=this.state.boxes.filter(b=>b.id!==boxId);
this.setState({ boxes});
}
render() {
//2.渲染 Mount周期
//在render里面会挂载新的组件,所以在render里面会递归调用每一个子组件的constructor() -> render() -> componentDidMount()
// console.log("App - Rendered");
return (
<React.Fragment>
{/* 当前boxes里的元素信息是存在了app里面,如果想在navbar里调用app里的内容,相当于是从上往下传数据,就可以通过props传数据了
这里把长度传给navbar
求一下有多少个元素不为0*/}
<NavBar boxesCount={this.state.boxes.filter(b=>b.x!==0).length} />
{/* Boxes放到container里面,bootstrap一般都会将内容放到container里面,container是一个自适应的内容填充区域,根据屏幕的宽度来自适应调节内容区域,可以让内容区域看起来他的宽度比较合适一些 */}
<div className='container'>
<Boxes
boxes={this.state.boxes}
onReset={this.handleReset}
onClickLeft={this.handleClickLeft}
onClickRight={this.handleClickRight}
onDelete={this.handleDelete}
/>
</div>
</React.Fragment>
);
}
}
export default App;