Best choice for multiple loop in page?

get_posts() is just a wrapper for WP_Query, however it does have two main differences:

  1. get_posts() just returns an array of posts, so doesn’t provide methods for a Loop.
  2. get_posts() sets the ignore_sticky_posts and no_found_rows arguments to true. no_found_rows is required for pagination, and disabling it improves performance.

#1 means means that you need to use a slightly different method for allowing the use of template tags like the_title(). It also means that hooks like loop_start and loop_end will not fire.

#2 means that get_posts() will be faster, and you don’t need to worry about sticky posts, which you almost certain don’t want included as part of a secondary loop.

However, you can achieve the performance of get_posts() with WP_Query simply by setting no_found_rows to true, and you can ignore sticky posts by setting ignore_sticky_posts to true.

So these are essentially exactly the same:

// WP_Query
$query_dog = new WP_Query( [
    'post_type'           => 'animals',
    'posts_per_page'      => 3,
    'ignore_sticky_posts' => true,
    'no_found_rows'       => true,
    'tax_query'           => [
        [
            'taxonomy' => 'species',
            'field'    => 'slug',
            'terms'    => 'chien',
        ],
    ],
] );

while ( $query_dog->have_posts() ) : $query_dog->the_post();
    get_template_part( 'template-parts/publication-animaux' );
endwhile();

wp_reset_postdata();

// get_posts
$posts_dog = get_posts( [
    'post_type'      => 'animals',
    'posts_per_page' => 3,
    'tax_query'      => [
        [
            'taxonomy' => 'species',
            'field'    => 'slug',
            'terms'    => 'chien',
        ],
    ],
] );

global $post;

foreach ( $posts_dog as $post ) : setup_postdata( $post ); // *Must* be $post.
    get_template_part( 'template-parts/publication-animaux' );
endforeach;

wp_reset_postdata();

Those two loops will have the exact same result, and nearly identical performance, theoretically. In the style I’ve written them they even have the exact same number of lines. So which one to use basically comes down entirely to which one you prefer.

The biggest difference is probably that the WP_Query method triggers the loop_start and loop_end hooks, which some plugins might use, you you might want to use (loop_start triggers on the first use of the_post(), and loop_end triggers on the last use of have_posts(). WordPress doesn’t do anything with them on its own.

I myself prefer WP_Query, simply because I find the direct use of the global $post variable and setup_postdata() to be a bit ‘hack-y’. I find get_posts() is more appropriate when you need to query posts for something other than outputting them in a loop. WP_Query on the other hand mimics the behaviour of the main loop, and