javascript React自定义钩子重新呈现

qjp7pelc  于 2023-01-16  发布在  Java
关注(0)|答案(4)|浏览(202)

我正在学习react,现在我有一个react应用程序,里面有用户和电影,你可以在登录的时候看到电影的信息。如果你是以管理员身份登录的,你可以访问一个管理页面,在那里你可以看到一个用户卡列表,并注册另一个管理员。这个卡片列表不需要刷新就可以更新。
因为我写的代码不是很干净,所以我想加入自定义钩子。问题是,使用新的自定义钩子,除了渲染之外,一切都很好。每当我删除一个用户或添加一个新的管理员时,除非我刷新页面,否则cardlist不会更新。
我现在有一个定制的钩子useUsers,但是我只在输入域和吐司通知中使用它,我试着在钩子中添加用户到useEffect,但是没有解决我的问题。
useEffect(() => { refreshUserList(); }, [users]);
这是我的代码。

function useUsers() {
    const [users, setUsers] = useState([])
    const [showModal, setShowModal] = useState(false)

    const notifyUserDeleted = () => toast.success('User deleted!', {
        position: "top-right",
        autoClose: 3000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: "colored",
    });

    const [adminUsername, setAdminUsername] = useState("")
    const [adminPassword, setAdminPassword] = useState("")
    const [showAdminModal, setShowAdminModal] = useState(false)

    const notifyAddAdminSuccess = () =>
        toast.success('Admin registered!', {
            position: "top-right",
            autoClose: 3000,
            hideProgressBar: false,
            closeOnClick: true,
            pauseOnHover: true,
            draggable: true,
            progress: undefined,
            theme: "colored",
        })

    const refreshUserList = () => {
        UserAPI.getUsers()
            .then(res => {
                setUsers(res.data.users);
            })
            .catch(err => {
                console.error(err);
            });
    };

    useEffect(() => {
        refreshUserList();
    }, []);

    const createAdmin = (adminUsername, adminPassword) => {
        const registeredAdmin = {
            "username": adminUsername,
            "password": adminPassword
        };
        UserAPI.createAdmin(registeredAdmin)
            .then(() => {
                setAdminUsername("");
                setAdminPassword("");
                notifyAddAdminSuccess();
                Adminpage.refreshUserList();
            })
            .catch(err => {
                console.log(err);
                alert("Failed to register admin!");
            });
    };

    const handleRegisterAdmin = (e) => {
        e.preventDefault();
        createAdmin(adminUsername, adminPassword);
        setShowAdminModal(false);
    };

    const deleteUser = (id) => {
        UserAPI.deleteUser(id)
            .then(() => {
                notifyUserDeleted()
                refreshUserList()
            })
            .catch(error => {
                console.error(error);
            });
    };

    const handleDelete = (e) => {
        e.preventDefault();
        deleteUser(e.target.value)
        setShowModal(false)
    }

    return {
        users, setUsers, showModal, setShowModal, notifyUserDeleted, notifyAddAdminSuccess, showAdminModal, setShowAdminModal,
        adminPassword, setAdminPassword, adminUsername, setAdminUsername, refreshUserList, handleRegisterAdmin, handleDelete
    }
}

export default useUsers;
function Adminpage() {
    const { users, refreshUserList } = useUsers();

    return (
        <div className="container" style={{ display: "flex" }}>
            <UserCardList users={users} refreshUserList={refreshUserList} />
            <InputAdmin refreshUserList={refreshUserList} />
        </div>
    );
}

export default Adminpage;
function UserCardList(props) {
    return (
        <div className="container">
            <h4 style={{ margin: "3% 0 2% 0" }}>User list:</h4>
            <Bootstrap.Row>
                {
                    props.users.map(user =>
                        <UserCard key={user.id} users={user} refreshUserList={props.refreshUserList} />
                    )
                }
            </Bootstrap.Row>
        </div>
    )
}

export default UserCardList;
function UserCard(props) {
    const { showModal,
        setShowModal,
        handleDelete
    } = useUsers()

    return (
        <Bootstrap.Col className="col-lg-4 col-12">
            <Bootstrap.Card className='mb-1' style={{ height: "98%", }}>
                <Bootstrap.Card.Body>
                    <Bootstrap.Card.Text><b>User ID: </b>{props.users.id}</Bootstrap.Card.Text>
                    <Bootstrap.Card.Text><b>Username: </b>{props.users.username}</Bootstrap.Card.Text>
                    <Bootstrap.Button style={{ backgroundColor: "red", borderColor: "gray" }} onClick={() => setShowModal(true)}><RiDeleteBin5Fill style={{ backgroundColor: "red" }} /></Bootstrap.Button>
                </Bootstrap.Card.Body>
            </Bootstrap.Card>
            <Bootstrap.Modal centered show={showModal} onHide={() => setShowModal(false)}>
                <Bootstrap.Modal.Header closeButton>
                    <Bootstrap.Modal.Title>Confirm Delete</Bootstrap.Modal.Title>
                </Bootstrap.Modal.Header>
                <Bootstrap.Modal.Body>Are you sure you want to delete this user?</Bootstrap.Modal.Body>
                <Bootstrap.Modal.Footer>
                    <Bootstrap.Button variant="secondary" onClick={() => setShowModal(false)}>
                        Cancel
                    </Bootstrap.Button>
                    <Bootstrap.Button variant="danger" value={props.users.id} onClick={handleDelete}>
                        Delete
                    </Bootstrap.Button>
                </Bootstrap.Modal.Footer>
            </Bootstrap.Modal>
        </Bootstrap.Col>
    )
}

export default UserCard;
pn9klfpd

pn9klfpd1#

问题

useEffect(() => { refreshUserList(); }, [users]);

users添加到useEffect钩子可能会导致渲染循环,因为refreshUserList最终会更新users的状态。不要无条件地更新任何钩子的依赖项。
React钩子也不共享状态。你有两个组件,AdminpageUserCard,每个都使用useUsers钩子的单独示例,每个示例都有自己的状态。改变useUsers的一个示例中的状态不会影响useUsers的任何其他示例。

溶液

将状态和逻辑从useUsers移到单个React上下文提供者,并允许useUsers钩子的所有示例访问单个上下文值。
示例:
x一个一个一个一个x一个一个二个x
UsersProvider组件 Package 应用程序代码,以提供用户状态和回调。

<UsersProvider>
  ...
  <Adminpage />
  ...
</UsersProvider>

现在,使用useUsers钩子在UsersProvider的子Reactree中呈现的所有组件都将访问和引用相同的状态和回调。

fcipmucu

fcipmucu2#

你所遇到的麻烦在React中非常非常常见:)每个钩子用法都创建了它自己的独立作用域,这意味着每个useUsers()用法都产生了单独的用户集合(从useEffect的后端中检索),并公开了单独的一组特定于用户操作的方法(如handleDelete)
首先,在回答第一个问题之前,让我们揭示一下你可能很快会面临的非常非常戏剧性的麻烦
只要每个“useUsers”执行都有自己的作用域,每次使用此钩子时,都会执行以下代码:

useEffect(() => {
    refreshUserList();  // UserAPI.getUsers()
}, []);

现在想象一下在Adminpage组件中调用这个钩子--用户从后端加载。加载用户后,用加载的用户呈现UserCardList。UserCardList为每个加载的用户呈现UserCard组件。最后,每个UserCard组件调用您创建的“useUsers”钩子,每个钩子调用上面的代码(useEffect -〉refreshUserList),这样每个UserCard组件都会从后端再加载一次用户:(
您看到了问题所在,您加载了20个用户,然后为每个用户再次加载所有这些用户。如果您有20个用户,则后端将有21个调用。如果您有100个用户,则后端将有101个调用,等等......后端的性能肯定会非常非常快地下降
为了证明这个假设请打开浏览器的网络标签,然后重新加载页面。你肯定会看到无休止的加载用户请求序列...
这是关于后端的,但是当用户被删除时前端不工作的最初问题仍然是开放的
情况如下:在Adminpage组件中使用“useUsers”呈现用户列表,并在每个UserCard组件中使用“useUsers”在需要时调用“handleDelete
这些组件中的每一个都独立加载“用户”,当用户通过UserCard组件中的handleDelete删除时-是的,您可以通过以下方式从存储中“物理”删除此用户

UserAPI.deleteUser

但是,在前端执行“refreshUserList”:

const deleteUser = (id) => {
        UserAPI.deleteUser(id)
            .then(() => {
                notifyUserDeleted()
                refreshUserList()
            })
            .catch(error => {
                console.error(error);
            });
    };

仅在当前UserCard组件范围内,并且仅在UserCard组件范围内刷新“users”集合
因此,父Adminpage组件不知道其中一个用户被删除,用户列表也不会更新
这里最简单的“工作”解决方案不是多次调用useUsers()钩子,而是将handleDelete从Adminpage组件直接传递给每个UserCard组件(作为输入参数):

function Adminpage() {
        const { users, refreshUserList, handleDelete } = useUsers();
    
        return (
            <div className="container" style={{ display: "flex" }}>
                <UserCardList handleDelete={handleDelete} users={users} refreshUserList={refreshUserList} />
                <InputAdmin refreshUserList={refreshUserList} />
            </div>
        );
    }

export default Adminpage;

function UserCardList(props) {
    return (
        <div className="container">
            <h4 style={{ margin: "3% 0 2% 0" }}>User list:</h4>
            <Bootstrap.Row>
                {
                    props.users.map(user =>
                        <UserCard key={user.id} handleDelete ={props.handleDelete} users={user} refreshUserList={props.refreshUserList} />
                    )
                }
            </Bootstrap.Row>
        </div>
    )
}

export default UserCardList;

function UserCard(props) {
    // do not call "useUsers" here !!!  instead accept needed callbacks as input parameters

    return (
        <Bootstrap.Col className="col-lg-4 col-12">
......... this code remains the same ..........

这种技术可以用最少的工作量修复您面临的所有后端/前端问题,但这种修复方法也有一些缺点:

  • 在组件树中,你只能使用一次“useUsers”钩子。你必须记住这个规则,并且总是小心使用它
  • 你必须在你的组件树中的所有组件中传递方法,就像我在上面的handleDelete中所做的那样(Adminpage -〉UserCardList -〉UserCard).对于你的组件树来说,这并不太复杂,但是对于更大的组件层次结构来说,这可能会变成“地狱”.想象一下,你在10个组件的层次结构中为5个回调做了这件事...

第一个麻烦(只有一个钩子用法)只有当你以某种方式连接你的“useUsers”用法时才能修复。也许,你可以利用一些类似rxjs的方法,在useUsers中使用订阅者/通知,在他们之间共享相同的“users”集合,并且每当“useUsers”中发生什么事情时-通知其他人,等等...其他可能的解决方案-利用一些应用程序范围的存储,我喜欢REDUX,并在那里存储用户。无论如何,这肯定是一个复杂得多的问题,然后你已经面临,这是“更好的”跳过它,如果不紧急
要解决第二个问题(通过层次结构中的每个组件传递callback作为输入参数),React的useContext()钩子可能非常方便https://beta.reactjs.org/reference/react/useContext

gdrx4gfi

gdrx4gfi3#

useEffect(() => { refreshUserList(); }, [users])

第二个参数[users]告诉useEffect什么时候触发,所以你实际上是在用户改变的时候刷新用户,这意味着永远不会,因为用户在刷新的时候也会改变。

vc9ivgsu

vc9ivgsu4#

Uladzimir提出的解决方案奏效了。我必须将所有内容从管理页面传递到组件中,如下所示:

function Adminpage() {

    const { users, handleDelete, handleRegisterAdmin,
        adminUsername,
        setAdminUsername,
        adminPassword,
        showAdminModal,
        setShowAdminModal,
        setAdminPassword,
        showModal,
        setShowModal,
    } = useUsers()

    return (
        <div className="container" style={{ display: "flex" }}>
            <UserCardList users={users} handleDelete={handleDelete} showModal={showModal} setShowModal={setShowModal} />
            <InputAdmin handleRegisterAdmin={handleRegisterAdmin} adminUsername={adminUsername}
                setAdminUsername={setAdminUsername} adminPassword={adminPassword} setAdminPassword={setAdminPassword}
                showAdminModal={showAdminModal} setShowAdminModal={setShowAdminModal} />
        </div>
    );
}

export default Adminpage;

相关问题