字幕列表 影片播放 列印英文字幕 [MUSIC PLAYING] BRIAN YU: OK, welcome back everyone to CS50 Beyond. We continue where we left off this morning. This morning we introduced React, a JavaScript-based framework to allow us to quickly and easily build user interfaces via declarative programming, describing what it is that we want the page to look like in terms of various different components. Then putting those components together to be able to create an application that's interactive. In order to update the application, we didn't need to do what we did before in the world of imperative programming of saying, go to this part of the page and insert certain components or other HTML elements inside this section of the page. We would just update our data, update the state of our components in order to say this value of the state should take on this new value. And React would take care of the process of updating the user interface to reflect whatever it is was contained inside of our state. So we're going to pick up where we left off, continuing in with the morning project. So there are a number of different ways to solve the problem in this morning's project. I'll show you a couple of them now just to give you a sense for the different approaches, the way that you can use React to implement some of these features. And then I'll introduce what the afternoon project will be. And the main focus of the afternoon lecture is just going to be thinking in React, thinking about what components we need, what state we need, how we might set up an application. And then we'll actually go ahead and try and implement the afternoon project. So let's go ahead and get started with the to-do list application, where we left it off this morning. So as of earlier this morning, we had a to-do list application that had a list of tasks, whereby I could type in a task, task one, and I click Add task. And that allows Add tasks to show up in this list. But of course, task one is still listed here in the input field. How do I get rid of task one from the input field? Going back to this morning, how would I remove task one from the input field when I click Add task, clearing out that field? Which function needs the change? Yeah? AUDIENCE: Send the value of input to empty string. BRIAN YU: Good, set the value of input to empty string. And where should I do that, in which function? AUDIENCE: In add task? BRIAN YU: Great, in add task. So when I add a new task, I want to do two things. In addition to taking my tasks array and updating it, filling it in with the new thing, I also want to say, all right whatever the value of the input field was before, just go ahead and set that equal to the empty string, because I want to clear out the input field as soon as I click the Add task button. So I'll go ahead and refresh this, try this out. I type in task one, click Add task. And all right, great, the task shows up and this field clears itself out. The other thing that we ask you to do this morning was show a count of how many tasks are currently in the task list. And someone else tell me how you did that. What did you change and what code did you have to write? I know many of you did this. So how did you do it? Show how many tasks I currently have. Yeah? AUDIENCE: Use .length. BRIAN YU: Yeah, if you have an array in JavaScript, and you use .length, you can get at the length of the array, the number of elements in the array. And so if I wanted to display the number of tasks that I currently have, I could just say number of tasks. And then using curly braces, again, to mean plug-in some JavaScript value here, where are my tasks stored? What would I type? In the state? OK, this .state.tasks. And then .length to get at, OK, here is the number of tasks that I actually have as of this moment inside of my application state. So I refresh that. And now I see, OK, tasks, number of tasks is zero. But as soon as I add a first task here and click Add task, multiple things happen. The list updates. We also see that this clears out and number of tasks updates to reflect the length of that array. Questions about any of that? All right, so the challenging piece of this, of the morning project was really the deletion of tasks. How do you take a task in this list of tasks and delete it so we no longer have it? And so let's take a look at how we implemented that. We'll scroll up to the top. For every list item, when we map over all of our tasks, right now we just have a list item that lists the name of the task just like this. If I wanted to do more than that, well, I could just add to this list item. In addition to just having a task, I'll also have a button, for example. And that button we'll just call delete. And let's leave it at that for now, just putting a button there. Right now the button doesn't do anything. But let's at least see it so we can visually see what that button looks like on the to-do page. We'll refresh the page. OK, number of tasks is zero. And I could say, OK, task one for example, add the task there. And all right, great, we see the Delete button. We also see task 1 show up next to it. They're a little bit close together. So you might try and modify some of the spacing of things, the margin around the button, for instance, in order to give yourself more space if you wanted to. But of course right now, even if I have multiple tasks, I can click Delete and the delete buttons aren't doing anything just yet. So how do we actually delete a task? Well, we probably want some function that's going to take care of deleting a task. And so one way to do this might be to say, all right, delete task. And I saw people approach this in a couple different ways. So I'll show you both ways that you could have tried to approach this problem. One way would have been to say, all right, delete task. Well, this is a function that's going to take some argument, some like index for which task it is that I actually want to delete. And the logic there would be, well, all right, when I click on the button, button on-click. Let me go ahead and delete that specific task corresponding to index I, which is the index I'm currently on. So you might have tried to write something like this .deletetask. And then in parentheses, you might have thought all right, let me put in the variable I there. Now, this doesn't quite work. Does anyone know why this doesn't quite work? It's a little bit subtle. Yeah? Exactly. Exactly. This on-click attribute should take as its argument a function. And the idea is that that function will not be called upon until we actually click on the button. But right now what we're passing into on-click is the result of calling a function. The fact that you see parentheses after this function means that we're actually calling the delete task function, which is not what we want to do. What we want to do is we want to pass the function into the on-click attribute, but not actually call the function until we click on it. So a couple ways to potentially handle that, the simplest way might have been just to wrap this whole thing inside of a function, where, oops, undo that. What did I just do? Delete task takes an index. I think I accidentally deleted that. And instead of saying on-click equals this .deletetaskI. We can wrap this entire thing in a function as simply as adding an extra set of parentheses and an arrow. Remember, that arrow syntax creates a function. And so we've created a function that doesn't take any arguments, but that when it's run, will actually perform the deletion of the task. So this is one way to do things. And so now what exactly does the delete task function need to do? Well, it needs to remove the task at this particular index. So I'm going to say this .setstate. And the new state is going to be a function of the old state. I'm going to take the old state, modify it by removing the task, and then returning the new state. So this is going to be a function that takes the old state. And then what do we need to do? Well, the first thing we need to do is get a copy of that list of tasks. Because I can't modify the state itself, as we mentioned before, you should never modify the state directly and react. So I'm going to create a variable called Tasks. And that's just going to be equal to this .state.tasks filled into a brand new array. So I create a copy of that list, save it inside of a variable called tasks. And then I want to remove whatever is at index I. And so as we've decided this morning, the way we would do that is by saying tasks.spliceindex1. In other words, saying remove whatever is at index--index. And how many things should I remove? 1. Now, the return value of splice is going to be the array that was removed, that was spliced out of the tasks array. But tasks itself, as a variable, is going to be the original array with whatever we wanted to remove removed. So there's a slight nuance there that I saw a couple people struggle with a little bit. Just know that what splice of returns is different than what the actual value of the tasks variable is. And now that I've spliced out whatever was an index, I can now use this variable tasks to be the new list that has the thing index--index removed from it. And so this is a function. So the last thing I need to do now is return something. And I'm going to return, all right, what should the new tasks be? It should be just this variable called tasks. And so this is a slightly more complicated set state function. In particular, it's more complicated because it's really more of a full fledged function where I'm defining variables, and manipulating those variables, and returning some value. Their add task meanwhile, was a much simpler function, whereby I could just say I didn't need to create any new variables. All I needed to do was say we start with this state and we're just going to immediately return this object that represents the changes that we're going to make to the state. And so I'd just take a look at Add task and Delete task, and see if you can get an understanding for the differences between the two. All we need to do in Add task is add to this tasks array. But in Delete task, we really need a little bit of extra logic to copy the array, remove something from the array, and then return what the new object should be. Questions about anything so far? This was one way to implement things from this morning. Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: If you're getting an error about-- so I would first check to make sure that your curly braces in parentheses match the curly braces in parentheses that I have here. You might be using the keyword in a place in your code that you're not allowed to use it. But if you're still getting that error after that, it's probably some syntax thing that we can take a look during the project time. Yeah? AUDIENCE: Why wouldn't we use this .setstate for delete task? BRIAN YU: We are using this .setstate for delete task. Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: In the Add task function-- so this is a little bit of a nuance of the way that arrow functions in JavaScript work-- if all we're doing in a function is taking some input and immediately returning its output without anything else, I can just say state as the input, arrow, and then immediately give whatever the thing I'm returning is, which is this JavaScript object. And so if that's all the function is doing, there's no additional logic, no loops, no variables, or anything like that, then you don't need to explicitly say return. Whereas in Delete task, the function is a little more complicated. I need variables. I need to manipulate those variables. And so at the end of that, I do need to explicitly say return something. Yeah? AUDIENCE: What is .something, [INAUDIBLE].. BRIAN YU: This is because I'm calling this .setstate. And the function that I'm passing into this .setstate is this entire thing. This function. And so this function, what it's returning ultimately is a new set of tasks that is going to become the new state. Yeah? AUDIENCE: Shouldn't this be just state, not .state? BRIAN YU: Oh, you're right, this should just be state, not this .state. Thank you. That's a good catch. Yeah? AUDIENCE: If you put the variable conts task outside of this .setstate, will that work? BRIAN YU: If you put conts task outside of this .setstate and you do something like this .state.tasks here, this will work in this example, but it suffers from the potential for the race conditions that we talked about earlier where if we're basing the new state of the old state, we should really be using the state input to the set state function. So in this example, it will work fine both ways. But good design habit to get into the habit of doing things this way. Other things? I think there are other questions. Yeah? AUDIENCE: I think we probably have [INAUDIBLE].. BRIAN YU: Yep. AUDIENCE: So we decided to practice using [INAUDIBLE].. BRIAN YU: Right, we're wrapping this delete task inside of a function so the delete task isn't run until we actually call this function. And this is a common way in programming to delay the evaluation of some expression is just to wrap the whole thing inside of a function. And only when you call that function will the value actually be evaluated. So this will work. This actually is a little bit inefficient in the sense that every time I generate a button, it's going to regenerate one of these new functions. And so I'll show you another way of doing the exact same thing. Totally fine to do this, but if you're looking for really trying to optimize your react code, I'll show you one other method of achieving the same goal, where instead of wrapping this whole thing in a function, I can just use this .delete task. Again, I'm not calling the function just yet. Delete task is going to be called eventually. But this time delete task is not going to take an index as its argument. But instead, what I'm going to do is I'm going to associate some data with this button. In the same way that in JavaScript before we could attach data attributes to HTML elements, we can do the same thing here, because this is just JavaScript, where I can say buttondata-index is going to be equal to I. Remember, data attributes are a way of adding information data that we care about to our HTML elements. And here I'm just adding I as the data attribute of this particular button. So now when someone clicks on delete task, this delete task is going to take an event as an argument, because we're going to need access to the event. Because if we try and get at the event.target, that's going to give us the button, the button that we clicked on. And if we access the data set for that button, the data attributes of it, and get at the index of that dataset, what that's going to do is it's going to give us access to the index of the button. It's going to take that button, get at its data properties, and get at the field called data-index. And that will give us access to whatever the index of this particular row happens to be. And so this will behave the same way, where I can say task one and task 2. And if I delete task two, all right, great, I'm left with just task one. And the number of tasks decreases from two to one as well, because React is dynamically going to update the DOM with any changes as a result of changing the state of my components. So two different ways of achieving the same thing. I'm just showing you both, because they're both the types of things that you might see in the world of programming in React. Questions about anything? Yeah? AUDIENCE: If we do it this, it's like a data attribute. For the index we use just value. Does that work for you? BRIAN YU: Yeah, you can use other attributes as well and access as attributes, and that would probably work. Yep? AUDIENCE: [INAUDIBLE] BRIAN YU: So again, delete task, just to review, is getting the index from the data attribute of the button. Then updating the state. And to update the state, we take the original tasks, splice out whatever was at index I, and then return the new tasks. And a small bit of JavaScript shorthand, which you don't need to know, but might be useful sometimes, is if ever you have a situation where the key of a JavaScript object has the same name as the value, you actually don't need to say tasks:task. If you just say tasks, that will implicitly just mean the key in a value have the same name. And so this is just a little bit of shorthand for doing the same thing. But no need to worry about that if you don't want to. Yeah? AUDIENCE: Are you worried about this being inside set state? Is it better to be outside? BRIAN YU: This, it doesn't matter whether this is inside or outside of set state, because it's not dependent upon the state variable. So you could calculate the index wherever. So long as you have access to the same event, the data index value is always going to be the same. So it doesn't matter if it's inside or outside of the function. Other things? Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: Yeah so these events you can think of as the same sorts of events we were dealing with in JavaScript before, where when I say button on-clicked equals this .addtask task for instance, then the event that is happening is the click event. And the target of the click event is the button that I actually use to do the clicking. And so the way this works in the case of delete task is that when I call this .delete task, it's going to be provided with the click event, the fact that I clicked on a button. And if I access event.target, that's going to give me which button I actually clicked on to do the deleting. So if I have 10 buttons, each of which has a different data-index property, then if I go to event.target and look at what data-index is, I'll know which button I clicked on and so I'll know which task I should remove from my list of tasks. Yeah? AUDIENCE: So [INAUDIBLE],, but basically what I did is after delete task, hit the button I, I used the index for I. And then down at the function delete task, I used [INAUDIBLE].. BRIAN YU: Yeah, so I showed two possible ways of doing it. And so one way is to do it the way that you're describing it. Yep. Yeah? AUDIENCE: Can you explain why the code is [INAUDIBLE].. BRIAN YU: So we only need event as an input to the delete task function because we need to use event to access the target, namely the button that we clicked on, because we need the button to get at the data attributes of the button. In the first method that we used, we didn't need to access any data attributes. So it didn't actually matter to have access to the button that was clicked on, because delete task was already being provided with the index. So we didn't need to access the event. Other things? Yeah? AUDIENCE: Why is this way more efficient? BRIAN YU: This way is slightly more efficient because in the other way when we created a new function for each of the on-clicks where we did curly braces arrow, this .deletetaskI, that would be a situation where every time it's creating a new button, it's creating a brand new function in order to put in on-click. Whereas here, I'm just using the same this .delete task function. And so it's just a slight efficiency benefit that's really not a big deal here. Though in larger applications, you might imagine it could start to become a big deal. In this case, it's probably fine just to avoid over optimizing. And either way is OK. All right, so really the best way to get good at React is to start writing applications in React. And we'll do some of these together. And so that's what the goal of today and tomorrow is going to be. React is a new way of thinking about writing programs. It's a new way of working with programs, and thinking about state and applications, and how to modify the user interface. And so we'll go through a couple now slightly larger projects that will step through the beginning parts together to give you a sense for the types of things that you can do with React and how to think about designing an application in React. And so the application we're going to build now is going to be a similar analog to the quiz application we had before. We're to create a flash card application. And so the flash card application is ultimately going to look something like this. When it's done, it's going to have a couple different modes. It will have a card Editor. And a card Editor basically allows me to input terms and definitions into the cards. So maybe I want to create addition flash cards, for example, to help me practice for the addition game that we did earlier this morning. So I do a front side and a backside. A front and a back for the card. Press Add card. And all right, that adds to this list of cards. I can add other front and back and more as well. If I wanted to delete a card, I can delete a card too. And then if I want to, once I'm satisfied with editing these cards, I can switch to the card Viewer. And the card Viewer is going to show me a card. If I click on it, it'll flip it over so I can see the back. If I click on it again, it will flip back to the front. And I can click on New card to get me a new card. Get me the next card in the list. When I click on that, it'll flip over, show me the back of the card, so on and so forth. I can switch back to the Editor. I can switch back to the Viewer. So this is the application we're going to build. And it seems a little bit complicated. But let's try and break it down into components. And what's the first thing that you might notice? What is this interface at least remind you of in terms of things we may have done today? Yeah, it reminds you very much, hopefully, of the tasks management application that we did this morning of just creating a to do list of tasks. This is effectively exactly the same thing, except instead of a task just having one field for the task, the task has a front and it has a back. It's two input fields instead of one. So this interface is probably largely going to resemble what it is that you did earlier this morning. We just now need to add a Viewer to it as well to be able to view those cards and flip the cards over from front to back. So questions about the goal-- what we're trying to achieve in writing this application? All right, let's go ahead and dive right in and actually start working on the application. So we'll go ahead and create a new file. We'll call it flashcards.html. And I'll go ahead and start with just going ahead and copying the to-do app just so we get the basic structure of it. But I'm going to completely empty out what's inside of class at, at least for now. We'll give it a title of flashcards. And all right, let's actually think for a moment. If I say term and definition, what are the different components that I might divide my application into? Process to different components of the application? There are at least two big components of the application, distinct parts of the application that behave differently. The Viewer and the Editor. Great, I have this Editor right here that is a place where I can edit tasks, add tasks to them. And I also have some Viewer that lets me view all of the cards that I currently have inside of my flash card table. And so those are probably at least two different components that are going to behave differently. So one thing I might do right away is say, all right, I'm going to have a class called card Editor that is going to be a component. And for now, let's go ahead and render that just by returning a div that says this is the Editor. So all right, I have a class called card Editor. And all the card Editor is going to do is say this is the Editor, at least for now. In addition to that, I probably also want a class called card Viewer that going to extend React.component. And we're going to render it by returning for now this is the Viewer. So all right, we have two classes, card Editor and card Viewer. And right now they just say this is the Editor, this is the Viewer. And I can test this, make sure it works by inside of my app. In order to render the app, let's for now just return a card Editor and a card Viewer. We'll load both components into the application, one on top of the other, just to see how this looks. I'll go ahead and open up flashcards.html. And all right, great, this is what I see. I see the card Editor that just says this is the Editor. And I see the card Viewer that just says this is the Viewer. But of course I don't want to display both the Editor and the Viewer at the same time. So let's step by step start building up this application. What is a piece of state that I need inside of my application? Yeah? AUDIENCE: Whether you're on Editor or Viewer? BRIAN YU: Exactly, whether I'm on the Editor or whether I'm currently on the Viewer. So it's some notion of, like, what the current mode of the application is. And so I could implement this by saying, all right, let's create a constructor that takes props, super props just to make sure that the component gets its props correctly. And let's give some state to this application. We care about knowing whether or not we're in the-- so we could do this state in a number of ways. We could say, all right, let's add a mode property to the state where the mode could start out being Editor, for example. And then mode could be any number of different things. But right now there are only two different modes. So I'll just say Editor is true. We'll have a piece of the state called Editor. By default, it is true. So by default, I'll see the Editor. And if ever the value changes, I'll show something else. And so, OK, what do I do now? Well, inside the Render function, we'll say something like if this .state.editor, implicitly if this .state.editor is true, then what do I want to do? Well, I want to return a card Editor. And otherwise else, I want to return a card Viewer. And I go ahead and delete this unnecessary return here at the bottom. So here is the render code for my app. I'm basically checking whether this .state.editor, should the state of my application be showing the Editor or not showing the Editor? If I am supposed to show the Editor, then go ahead and return the card Editor. Otherwise, go ahead and return the card Viewer. So if I load the page now, refresh flashcards.html, I just see this is the Editor and I have no way of accessing the Viewer. I only see the Editor. Questions about anything? BRIAN YU: All right, so let's take the next step. I can have the Editor. And now I want some way of flipping back and forth between Editor and Viewer, between Editor and Viewer, going back and forth between these two components. And so I probably need some way inside of my app to change the value of this Editor piece of state. I want some function that is going to change whether Editor is true or false. So I'll go ahead and add a new function. And the new function, we'll call it switch mode. Switch mode is going to be a function that takes me from Editor mode to Viewer mode, and from Viewer mode to Editor mode. So it's going to be a function. In order to do that switch, I need to set the state to something. So I'm going to start with some starting state. And what is the new state? What goes in here to switch the mode of my application? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yes, so we have inside of this .state a notion of where we are. If Editor is true, we should be on the Editor. If Editor is false, we should be on the Viewer. So yes, good, we need that. And, yes? AUDIENCE: Editor, [INAUDIBLE]. BRIAN YU: Yeah, exactly. Editor, we're going to change the value of Editor to exclamation point for not state.editor. So whatever the value of state.editor was before, let's take whatever is not that, so if it was true, it's now false. It was false, it will now be true. And that will be the new value of Editor, for example. So all right, now what I need is I would like some way for maybe the Editor to have a button and the Viewer to have a button that will result in switching the mode of the application, for example. And so how do I go about doing that? Well, there are a of ways to do it. I could put the button directly in the app right here and just have a button at the bottom that's going to say, like, switch mode or something. Another way to do it would be inside of card Editor for instance, let's go ahead and add maybe a horizontal row. HR just creates a horizontal row, a line, across the page effectively, and a button that says go to Viewer. And in the Viewer, go ahead and create a horizontal row and a button that says go to Editor. So OK, I have the task Editor that has a button that says go to the task Viewer. And I have the Viewer that has a button that says go to the Editor. If I refresh the page, it says this is the Editor and there's a button that says go to Viewer. Of course if I click on that, nothing happens. Nothing happens because I haven't specified what should happen when I click on this Go to Viewer button. What I would like to happen, if I click on the Go to Viewer button in the card Editor component, is that it calls the switch mode method of my app component. But there is a slight problem, what's the problem? Why can't I just say switch mode? Yeah? AUDIENCE: They're in different classes. BRIAN YU: They're in different classes. Exactly. Switch mode is a method inside of the app component, but I want to be able to call it from the card Editor component. They're in different components, but I want the card Editor to still be able to call a function inside of the app. So how would I do that? Any thoughts on how I might solve that problem? The function is inside of the app and I would like to give it to the card Editor. In other words, pass information about that function or pass the function itself to the card Editor so the card Editor can use it. Thoughts on how to solve that problem? We saw the solution this morning. Yeah? AUDIENCE: Could you have it extend the app? BRIAN YU: Could you have it extend the app? Yes, you could. Generally, React doesn't recommend that you have different classes extending other classes, because generally the classes want to do pretty distinct things. But that could be one solution to the problem. Yeah? AUDIENCE: You can use a property. BRIAN YU: You can use a prop. Great. And that's going to be the solution we're going to use. We learned already that if I want to pass information into the card Editor, I can add props to the card Editor. I can say, all right, give the card Editor a switch mode property that is equal to this .switchmode. Remember, I'm inside of the app component right now. And when I load my card Editor component, I would like to provide it with a property called switch mode. And that is equal to the this .switchmode function. I'm passing this function into the card Editor component so that the card Editor component can use that function. It can be helpful here to think about the hierarchical structure of the application. We have the app, which is the big container, inside of which is the card Editor. And the app is going to give the card Editor this, this.switchmode function so that the card Editor can now use that function. Yeah? AUDIENCE: Let's say you have a function that every single last [INAUDIBLE] would use. If you could find that function, [INAUDIBLE].. BRIAN YU: That's a good question about if you had a lot of components that were all trying to take advantage of functions that are modifying the state, with just React, you'd have to do something along these lines. Although, there are other libraries. In particularly, a library called Redux, which is quite popular, which helped to make that process easier and can have some useful abstractions for simplifying that process. We're not going to get our chance to talk about it this week. It's a little beyond on the scope of the class. But Redux is what you should look into if that's something that you're trying to do. So OK, I provided the switch mode property into the card Editor. And now inside of card Editor, what should button on-click do? What is the function that I need to call when the button is clicked? AUDIENCE: This .prop. BRIAN YU: This .props.switchmode. Great. I want to access the switch mode function, and the switch mode function was provided as one of the props of this card Viewer component. So when I click on the button, it should call this .props.switchmode. I'll go ahead and refresh the page. It says this is the Editor. I click on and go to the Viewer, and, uh-oh, something went wrong. Why did that not work? This .props.switchmode. AUDIENCE: [INAUDIBLE] BRIAN YU: What did I not do? I didn't edit-- oh, I edited the Viewer class. Yes, thank you. I'll go ahead and make the same change to the Editor class button. When you click on it, unclick this .props.switchmode. Thank you for that. OK, now if I refresh the page it says this is the Editor. If I click on Go to Viewer, it switches. Now I'm on the Viewer, I click go to Editor and why did that one not work now? Oh, why did that one not work? Why did card Viewer not work? Anyone know? AUDIENCE: [INAUDIBLE]. BRIAN YU: It has this .prop.switchmode. But what did I forget? AUDIENCE: [INAUDIBLE]. BRIAN YU: I need to give it the prop. Exactly. So I have card Viewer, but I didn't provide it with a switch mode prop. And so I need to say card Viewer and switch mode prop is going to be equal to this .switchmode for example. So now I have the Editor. I can switch to the Viewer and hopefully I can switch back to the Editor. You can go back and forth between the two just by changing the state of the application. Questions about anything so far? All right, so let's go ahead and try and build out this Editor. We'll try and build the Editor together. And then I'll leave most of the Viewer to you to try and figure out if you'd like to. So inside of our application, we currently have some state that indicates whether we're on the Editor mode or on the Viewer mode. We're also going to need some state about, like, what the current cards inside of my collection of flash are. Like, what are the cards, what's on the front, what's on the back of them. And an interesting question comes about of where should that state live, should that state be inside of app, should it be inside of card Viewer, or should it be inside of card Editor? Each is a component, each can have its own state, which component should maintain the list of all of the cards? What do we think? Yeah? AUDIENCE: [INAUDIBLE] Editor. BRIAN YU: Yeah, exactly. We want the state about all the cards to be accessible to both the Editor and the Viewer. So it probably makes sense to put the state about what all the cards are inside of the app, give the state access to cards, which by default will just be an empty list. That way we can provide that information to both the Editor and the Viewer, and both can have access to that same state. And this is an idea you'll commonly see in React called Lifting State Up that you might think, OK, the card Editor needs to have access to state about all of the cards. But if we lift the state up out of the card Editor and into the application, then anything the application uses, any of its components can have access to that same piece of state. Questions about that? All right, let's go ahead and try and build the card Editor now. So importantly, when I have the card Editor, the card Editor needs to know what the cards are. So I'm going to go ahead and provide the card Editor with an additional property. And that property is just going to be called cards, I suppose. And cards should be equal to what value? AUDIENCE: This .state.cards. BRIAN YU: This .state.cards. Great. This is inside of my app component, and so when I have the card Editor, I want to provide it with cards, and its value should be this .state.cards. You'll notice this line is starting to get a little bit long. So it's often common in React paradigms to see each prop to be on a new line of its own. And so you'll often see something that looks a little something like this, where I'll return card Editor. Cards is equal to this .state.cards. And I'll also give it access to switch mode. And I'll do the same for card Viewer, give it access to cards in this .state.cards, give it access to switch mode for the card Viewer. They both need access to that same information. So now let's go ahead and build our card Editor. Right now all it does is just say this is the Editor. Let's add some HTML to it. At this point, I can just write HTML and declaratively describe what it is that I want the Editor to look like. Well, it should be in H2, maybe, that says card Editor. You can make it in H1 also, make it a little bit smaller. And underneath the card Editor, I want there to be a table. A table where I have rows and columns, a way to show the front and the back of each card, and then a button to delete each card. So underneath this, I'll say table. The table is going to have a table head and a table body, either just HTML just to make it easy to separate the heading of the table from the body of the table. And the heading of the table is going to have a table row, just a row along the top of the table. And that row is going to have three columns. The first column will be, OK, this is the front of the card, this is the back of the card, and here's a button to delete this particular card. So I have a row at the top of the table, front, back, delete. And inside the body of the table, here is now where all of the rows are going to go. And oftentimes an easy way of just doing this is by declaring a variable and then we can go back and actually declare it. I'll just say curly braces rows, meaning the rows of the table are going to go here. Now, rows is a variable I haven't yet actually defined. And so I can define a variable up at the top of the render function. I can define a variable called rows, which is going to be equal to, all right, well how am I going to get all the rows? Well, this is going to look very similar to how we got that unordered list of items inside of our task list. What I'm going to say is, where are the cards stored, this .state or this .props? Props. Great. Why props? Why are the cards in the props? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, so in app when we were creating the card Editor, we passed in cards as one of the properties of the card Editor. And so now in order to access those cards, we can say this .props.cards. And then I want to again map over each of these cards, doing something with each card, in particular, creating a table row for each one of them. So I'll say map and we'll go map over every card, and also give it an index variable I so we have access to which card it actually is. And we're going to go ahead and return a table row. Each card is going to correspond to a single table row. We'll give that table row a key, because React likes to have anything that's iterated over a key that is unique. And we'll give it three cells inside of that row, a front, a back, and a Delete button. For the first cell, we'll say card.front. We'll make each one an object. Right now we haven't actually seen how we create a new card yet, but this is how we would print them out for instance. Then we'll have another one that says card.back. And then we'll have another one that's just a button that says delete. So OK, I've defined rows, which is going to be taking all of the cards and mapping over them, looping over all of the cards. And for each card, creating a new row. That row has three columns, one column to represent the front of the card, one column to represent the back of the card, and one column to be a button that I can click on to delete the card. Very similar again to the task management example from earlier this morning. So hopefully you can see the parallels that are there as well. Take a look at the example from this morning at the map that we were doing over the list of tasks. And you'll see a lot of similar code that's being used here, a lot of similar ideas in React. To test this out, rather than starting cards off with an empty thing, I can just for the sake of example give it, OK, front is going to be test front and back is going to be test back. And we'll go ahead and add a second one, front equals test 2 front, back is test 2 back. Just giving it some default state that I'll delete eventually, but that'll be useful for now until I have the ability to add cards to be able to see what are the cards that I currently have inside of my application. If we open up flashcards.html, all right, great, we see now that we have a card Editor and we have three columns, front, back, and delete. The Delete button doesn't do anything yet. But at least now I can see the contents of my Editor. What questions do you have so far with what we've done just now? We just created an Editor where we can see the contents of whatever flash cards we currently have. I'm going to add some CSS code just to make this look a little bit nicer. If we go up to the top of our HTML page, this is very similar to CSS code I've added before. So we'll go through it a little bit quickly. But feel free to stop and I'll post this code once we move into project time if you want to take a closer look at it. But basically tables, and table data cells, and table heading cells, I should probably change the headings to table headings. I'll actually do that now. The headings should probably be th instead of td. Effectively, this isn't changing a whole lot. But it helps to make sure that HTML knows that these are in fact headings for the tables. So the table, table data cells, and table headings, those will have a border that is a one pixel solid block border for example. And so, OK, what does that look like? All right, we have a one pixel solid black border around each of the cells. I don't like the fact that there is two lines between a lot of the information. And so in order to collapse that inside of table, we can use the border collapse is collapsed CSS property. That'll basically take all of the neighboring sections of the border and just collapse them down. And now this feels a little bit cramped. So I'd like to add a little bit more space in between these various different elements. So for table data cells and table headings cells, let me add 10 pixels of padding on the inside of each of those cells. And that will be space on the interior of the border such that when I refresh the page now, all right, this looks a little bit better, at least a little more aesthetically pleasing. And you should feel welcome to mess with the CSS all you want. You can add colors, change the border if you would like to. You can center the table if that looks better for you. So feel free to make any changes to the style and the aesthetics of the code, and also the functionality of the code if you choose to do so. Questions about anything thus far? All right, so we have this table. And now in addition to having a table, I also need some way of adding a new card. So I'll go ahead and say line break here. And I'll have an input whose name is going to be front and that'll be it for now. And an input whose name is going to be back, and actually I'll give each of them a placeholder at least. I'll call it front of card and place holder back of card. So we have two input fields now. And I'll have a button that says Add card. So I have two input fields, one called front, one card called back. And a button that's going to add a new card to this list of cards. Press Refresh. Some error somewhere. I executed closing tag for input. Oh, I need a flash to close these input tags. All right, now I have a card Editor. It's got a table. And I also have a place where I can type in the name of the front of the card, then whatever goes on the back of the card. And then a button to actually add that card to this table. So now what state do I need to keep about the card Editor? What can change about the card Editor? Similar in spirit to what we had with the to-do list, what are two pieces of state that I need? Yeah? AUDIENCE: The input. BRIAN YU: The input. Yeah, what it is that I'm typing into the front field, and what it is that I'm typing into the backfield, that all needs to be state inside of my card Editor. So I'll go ahead and create a constructor that takes in some properties. And I'll set the initial value of this .state equal to, all right, well, the front of the card by default is just going to be empty. And the back of the card is by default also just going to be empty. And that's going to be the value of this .state. If I scroll down to these two input fields, this is the input field where I type in what goes on the front of the card. This is the input field where I type in what goes on the back of the card. The value of this input field is going to be this .state.front, whatever it was on the front of the state. And the value for the back, this .state.back. I'm basically doing the same thing I did with all the input fields before, just giving them a value so that I can later refer back to them. Both of them need an on change property for what happens when I actually start typing something into this input field. And I'll just say this .handlechange as the name of the function for when I actually start typing something into the front field or into the backfield. For the back one, I'll do the same thing, onchange=this.handlechange. And now let's write the handle change function for what should happen when I start typing something into the input field. And let me do that underneath at the end of the render function. Handle change is going to be a function that takes its event. And handle change is going to need to modify the state of the application. And if I wanted to change the front of the value front inside of the state, I could say this .state front is equal to event.target.value. And that would change the front value. And if I wanted to change what was on the back of the card, I would have to do this .state back equals something else. And so this is a little bit problematic, because I'm using this .handlechange for both front and back. I'm using the same event handler, this .handlechange for both of them. And so I need some way of differentiating between these two event handlers or saying, if I type something into the front input field, then I want to change the value of this .state.front. And if I type something into the back input field, then I want to change this .state.back. And so taking a look at these input fields, is there any property that would be helpful for doing that? Take a look on 63 and 64, any attribute these elements have that would be useful. Yeah? AUDIENCE: You could somehow put the name [INAUDIBLE].. BRIAN YU: Yeah, exactly. I could somehow take advantage of the fact that the name attribute of this input field is front and the name attribute of this input field is back. And I would like to if the name is front, set the state for front to be event.target.value. And if it's back, then set it to the back value. And so JavaScript has some special syntax for doing this. It's not something you've likely seen before. But if I in brackets say [event.target.name], well that's going to say whatever the value of event.target.name is, let's go ahead and use that as the key. And then event.target.value is going to go ahead and be the value. So I can use a single handler to be able to handle changing both the front and the back value of the state. So something you might see from time to time, this is common when you have multiple different input fields. And rather than create a handle change front function and a handle change back function, which would just be repetitive, you can just have a single function that takes care of all of that. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, so I could add logic. I could equivalently do this by doing ifevent.target.name=front, then do something, and else do something else, and that would work too. And again in React as with almost any programming project, there are many ways of achieving the same things. So pick the thing that makes the most sense to you is often the best piece of advice. So all right, I have a card Editor now where I can type something into the front of the card and something else into the back of the card. And clicking Add card right now does nothing. So let me go ahead and write a function that is going to add a new card. Now to add a new card, do I need to modify the state of card Editor, card Viewer, or app? Again, I have three components. Which state do I need to modify to add a new card? App state, right. Because app, that class, is the one that actually has cards inside the state of the application. So we're going to need to add a Add card method to this application. And the Add card method, we'll have it take two arguments. We can choose what the arguments are. But it's probably going to make sense for the Add card method to take an argument for the front and an argument for the back. And then do something with those two values. So to add a card with a front equal to front and a back equal to back, I can do this .setstate. I want to update the state of my application. And I want to set cards equal to, well, I want all the existing cards plus my new card. So I'll say ...state.cards to say fill in all of the existing cards into the array. And now I'd like to add my new card to this array as well, where the front is going to be equal to front, and the back is going to be equal to back. So I'm updating the state, saying take the old state, go ahead and fill in all of those cards, and then just tack on to the end of the array a new object where the front of the card is this variable front, and the back of this card is this variable back. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, excellent point. So I mentioned this a moment ago of JavaScript object shorthand, because front's key and front's value are the same name, they're both called front and back, they're both called the same thing, this happens often enough in JavaScript that there's shorthand for it. I can just say, front, back and it's just going to assume the keys and values have the same name. And so this will do exactly the same thing. But yeah, that's the shorthand I can use. Yeah? AUDIENCE: Can you repeat [INAUDIBLE]. I'm a little bit confused with event. Why does switch mode not take an event, if it also [INAUDIBLE]?? BRIAN YU: So why does switch mode not take an event. Switch mode could take an argument called event. JavaScript option has functions that have these optional arguments that if you wanted to take an event argument, it can. But switch mode doesn't need to access the event. Because the only thing the switch mode function needs to do is take, whether it's on the Editor and the Viewer, and switch it to the other Viewer, the other component that I have. And so nothing about the event actually matters for the logic of the switch mode function. Whereas by contrast, handle change actually does care about the event. The event of me changing the input field is important for two reasons. One, I care about the event, because I care about the name of the input field that I was typing something into. Was I typing something into the front field or the backfield? And depending on that, I want slightly different logic. And it also matters because I care about what is it that I actually typed in. Because what it is that I typed in should be the new value of either front or back in the state. So if you need to access the event for some reason, usually to get at event.target, the thing that's triggering the event, then you'll need the event parameter in the function. But if you don't need the event, then it doesn't matter whether you include it or not. And so generally speaking, we won't include it. Yeah? AUDIENCE: For all the examples you've been doing [INAUDIBLE] Is there a reason we're doing that instead of on the bottom click maybe change the state of the [INAUDIBLE].. BRIAN YU: So when the button is clicked, we want to be able to just look at the state of the application to be able to know what is the front value and what if the back value. And so generally in React, any time something changes in the user interface, that should be represented by some underlying change in state. In other words, if you know the state of the application, what the value of this .state is for all of your components, I should be able to tell you exactly what the page is going to look like. And so that's just sort of part of the React paradigm. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: It depends on what you try to do with properties that you don't provide in. So if you try to use a property that you don't provide and just, like, paste it into the HTML content somewhere inside your render function, odds are it will work but just nothing will show up there. But if you try and do any logic with it, like, if you try and call or pass it in as arguments to functions or perform operations with it and they don't exist, then you could start to get errors. So you do want to be a little bit careful about that. Other things? OK, so we have this Add card function. And in particular, this Add card function is a function that our card Editor needs to have access to. And so I'll go ahead and add to this card Editor. The card Editor knows how to switch modes. But the card Editor also needs to know how to add a card. So Add card will be equal to this .addcard. And now inside of our card Editor, when you click on the button, on-click, I'll call this .addcard. So this is a different Add card function. But what needs to happen when I add a card in a Viewer? Well, two things need to happen. One is I need to actually call the Add card function in my props. This .props.addcard, passing in whatever was in the front input field and whatever was in the back input field. And that information is stored in this .state.front and this .state.back. And then what else should I do from a user experience perspective if I type in something into the front input field, type in something into the back, and click Add card? AUDIENCE: Clear out the states. BRIAN YU: Clear out the state's. Clear out the input field so that we say this .setstate. Front is going to be empty. Back is going to be empty. And so we're saying clear out those input fields so that we can start fresh. Go ahead and refresh the page. Front of the card, it will be test three front. Back of the card, test three back. I'll go ahead and click Add card. And all right, great, we got a new card in this list of cards. And the input fields cleared themselves out. At this point, I'm going to go back and delete these sample cards that we had just for testing purposes. We'll go back to just starting with an empty list. And now I can begin to add whatever flashcards I want. I can say, OK, 1 plus 1, plus 2. We can do as many of these as you want to. But of course, the Delete button doesn't yet work. We can create all the cards, but we can't yet delete a card. And so to do that, it's going to be basically the exact same thing as what we just did for adding a card, but the analog for deleting, as we did with the tasks example. So inside of the app, we had a function called Add card, which took care of adding a card. But our app is also going to need to know how to delete a card. And to delete a card, we're going to delete a card based on its index. And this code should look very familiar to the code we did when we were doing task management. We're going to say, all right, this .setstate. It's going to take the original state. And let's go ahead and say, all right, the cards, let's make a copy of state.cards. And let's go ahead and splice out of the cards remove something from the cards array at index. Remove one thing and go ahead and return cards is equal to whatever the value of cards is. So what's happening here? When I delete a card, at this particular index, I'm saying, OK, make a copy of state.cards, store it in this variable called cards, remove what was ever at this index, remove one element using the splice method. And then I'm returning the new state, update the value of cards in the state to be equal to this variable cards. And as we've seen a couple of times now, because these things have the same name, you could even simplify this to just return cards. And we'll implicitly assume that they have the same name. So now I have this delete card method in my app that operates in order to delete a card. This is also a function that card Editor needs to have access to. So we'll say delete card equals this .deletecard. And we'll go ahead and now go to the card Editor. And we'll do the same thing we did before, this button will give a data index property of-- I'm sorry, not the Add card button. The Delete button-- yeah, here's the Delete button-- will give it a data/index property of, all right, what is the index of which element to delete if I try and delete this card? It'll be I for example. And then when I click on it, we're going to call the this .deletecard method. Same as the example from earlier this morning. And now when I delete a card, that's going to be a function that takes the event as input. And why do we need the event? Well, because event.target.dataset.index. In other words, take the click event, get out its target, the button that I clicked on, get at its data attributes, and specifically, get at the data-index attribute of the button then I clicked on. This is the index of the card I want to delete. And so to actually do the deletion, I'll say this .props.deletecard. I'm deleting whatever was at that index. So I have a delete card function inside of the card Editor that calls the delete card function that was provided to me as one of the props. And the only difference is this function, delete card, is going to take care of providing to the apps delete card the index that I actually want to delete. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Repeat that? AUDIENCE: [INAUDIBLE]. BRIAN YU: We don't need to wrap it because we're not actually calling this .deletecard. I'm not saying this .deletecardI, which you could do. I'm just saying this .deletecard. And so that is a function that we'll run when the button is clicked. And so there's no need to wrap that inside of an outer function. Yeah? AUDIENCE: Why can you find delete card twice separately rather than just call the card function that's defined in the app and [INAUDIBLE] BRIAN YU: So you could do it that way. You could wrap this in a function and say, this .props.deletecardI. And that would work just fine. Basically, having a function that when you call it is going to go to props.deletecard and delete that element. But I'm putting it separately for the efficiency reason that I described before of not regenerating these functions over and over. Let's check to make sure this actually works. So let's do test, test. Another one, another one. If I delete a card, all right, it gets removed. If I I delete another card, we're able to add and delete cards from the card Editor. Questions about anything? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Good question. So the delete card function inside of the app class is a delete card function that just takes an index and removes whatever card is at that index. And I had another function in card Editor that I could have given a different name. But it's deleting a card, so I decided to give it the same name. And all this function is doing is it is calling the original function, passing in which card to actually delete. You could conceive of other models where you consolidate these into one function. I separated into two for the sake of separating the behavior of deleting a card at a particular index and responding to the fact that you clicked on a button that is supposed to do the deleting of the card. Though if you'd like to practice, it would be an interesting exercise to consolidate this to just a single function. And you can try doing it that way. All right, so now we have the ability to edit cards. And all that remains is this card Viewer. And so we have this, this is the Viewer page where in theory you'd be able to view whatever contents of the cards that you have, because card Viewer is going to provide this cards argument that is going to contain all of the cards in the current state. Questions? OK, so there's a lot of code here, a lot of interacting and moving pieces. We have an app that is this big component that contains within it two smaller components, a card Editor and a card Viewer. And there's a lot of interaction between these components. The app is providing props to the card Editor, providing information about the cards, providing event handlers for what to do in order to add a card or remove a card. And in response, the card Editor is calling a lot of those functions that it was provided in order to say, OK, update the application state, update the cards or change the mode from the Editor to the Viewer, or vise versa. So I definitely encourage you to take a look at this example-- I'll post it online in just a moment-- and try and tease it apart. Try and figure out how it's working. In fact, potentially try and add new features to it if you'd like. And the main project of this afternoon is going to be to extend on this. The first step is going to be understand the code that exists there. But then let's think about trying to add some more possibilities. So adding flash cards is a feature that we already have. Displaying the flash cards in a table, the second thing here is a feature that we already have. And it's these last two bits that are worth trying to implement here. Try and implement the card Viewer, some way of viewing the flash cards. And at first, just see if you can get one flash card to display the front of card number one, for example. And then see if you can add some notion of, all right, when you click on something, have an on-click handler that flips the card over. In other words, goes from storing the front to the back. And you might think about the kind of state that you want to store that. You probably want something inside your application state that's keeping track of are you on the front of the card or are you on the back of the card? And when you click on the card, you just need to flip that state around going from front to back. In much the same way that we went from Editor to Viewer, when you clicked on a button, you can go from front to back of the card by clicking on the card. You can display one card, and show the card, and flip it over to the back. Consider a button that moves you on to the next flash cards. So you're on card number one, you press a button, it takes you to card number two. And that's very similar in spirit to just this idea of thinking about what you need to store inside of your application state. In addition to having information about are you on the front of the card or the back of the card, you probably also want some information about which card are you currently on? Some notion inside the state of I am currently on card zero, or card one, or card two, whereby you can change that state value in order to show a different card. So go ahead and give that a shot. If you manage to finish that, there are certainly other features you can consider. I listed a couple here. Consider a button that will shuffle the cards and put them in a random order, for instance. Or marking cards as learned or not, the one you might see on a quiz app like Quizlet, for example. And so plenty of room to explore here as well. Feel free to continue working on the to-do list application if you'd like to. The staff will be around to certainly help you up until 5:00. And tomorrow, we're going to spend even more time on React. So if this all seems new and unfamiliar to you, know that tomorrow we'll be spending more time on the exact same topic so that we can help you become familiar with it, help you get used to the different way of thinking with regards to React. And so all that and more with CS50 Beyond. So we'll go ahead and turn to our afternoon project now. Feel free to stick around here in the auditorium. And come and find the staff if you need help with anything.
A2 初級 React,續--2019年以後的CS50。 (React, continued - CS50 Beyond 2019) 1 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字